Give a team its own view
A district crew sees and acts on its own bins only, the city sees everything, and every Action is audited.
One twin of a city's waste bins, two audiences: the city's operations room sees every bin, and the north district crew sees and acts on the north district only. You do not copy the twin; you define two Worlds over it.
import { auditActions, createAccessPolicy, createMemoryAuditLog, type World } from "@klorad/access";
import { createScene } from "@klorad/api/world";
const scene = createScene({ coordinateSystem: { origin: { lat: 40.62637, lon: 22.94838 } } });
scene.add({ id: "n1", name: "Bin N1", position: { east: -30, north: 70 }, correspondence: "shadow", binding: { source: "bins", entity: "n1" }, properties: { district: "north" } });
scene.add({ id: "s1", name: "Bin S1", position: { east: -25, north: -80 }, correspondence: "shadow", binding: { source: "bins", entity: "s1" }, properties: { district: "south" } });
const worlds: World[] = [
{ id: "city", name: "City operations", scope: { all: true }, capabilities: { actions: "*" }, principals: [{ kind: "role", id: "admin" }] },
{
id: "north",
name: "North crew",
scope: { properties: { district: "north" } },
capabilities: { actions: ["mark-emptied"] },
principals: [{ kind: "team", id: "crew-north" }],
presentation: { primary: "#0e7490", visibility: "authenticated" },
},
];
const audit = createMemoryAuditLog();
const policy = createAccessPolicy({ scene, roles: { admin: "*", crew: ["mark-emptied"] }, worlds, audit });
auditActions(scene, audit);
const kostas = { id: "kostas", roles: ["crew"], teams: ["crew-north"] };
const entered = policy.worldsFor(kostas).map((w) => w.name); // ["North crew"]
// What Kostas receives: a scene with Bin N1 only, kept in step with the twin.
const view = policy.view("north", kostas);
const visible = view.scene.objects().map((o) => o.id); // ["n1"]
export { entered, visible };Designing Worlds
Scope by properties, not by lists. scope: { properties: { district: "north" } } keeps
working as bins are added; a list of ids needs maintenance. Use within (a centre and a
radius) for geographic areas, and correspondence to show, say, only shadows.
Give capabilities, not roles, to Worlds. Roles say what a person may do anywhere in the organisation; a World's capabilities say what may be done in that World. The person needs both, so the crew role can be broad while the World stays narrow.
Principals can be users, teams or roles. Prefer teams and roles: people change jobs, and a World that lists individuals goes stale.
Hand members the view, never the twin. policy.view(worldId, actor) throws if the actor
may not enter, and returns a scene with in scope objects only. Build the member's interface on
that scene, and objects outside the World never reach their browser.
Auditing
auditActions(scene, audit) records every Action outcome, including denials, and the policy
records World changes made with setWorld and removeWorld. In a server app, implement
AuditSink over an append only table.
Something wrong or unclear? Tell your contact at Prieston Technologies.