Skip to content

A badge attributes, never grants

The physical worldScanDashboardHTTPidentity schemeactingTokeneph:v1:acting_identityrequireActingIdentitybadge_grant

A badge scan mints a short-lived acting grant; every act then runs as the badged person under their own live RBAC.

The situation

A shared tablet at a bench is used by everyone on the shift. Each act must be owned by the person who did it, and a badge must never let someone do what their own account cannot.

The recipe

An identity-kind scheme binds badges to people through a lt_users.metadata key.

start.ts
scanSchemes: [{
version: 11,
name: 'Badge',
kind: 'identity',
target_facet: 'badge_id',
grant_ttl_seconds: 600,
grant_max_uses: 1,
grant_scope: 'action',
rules: [{ category: '0', name: 'Badge', steps: [],
fallback: { markdown: 'Unknown badge. See a lead.' } }],
}],

A matching scan answers identity_primed with the person’s name, an expiry and an ephemeral grant (eph:v1:acting_identity:<uuid>). The station sends it with later scans as actingToken. Steps and choices opt in with requireActingIdentity: true. A role can set its stations’ policy in properties.badge_grant, for example { "ttl_seconds": 600, "max_uses": 0 } for ten minutes of acts.

The guarantee

The grant confers attribution, never privilege: a badged person without write scope on the queue is still denied. Mutations record the person (assigned_to, resolved_by) with the device beside them (scanStation). A grant is spent by acts, not by looking, and a write that does not land gives the use back. A dead grant (expired, exhausted, revoked) is a loud not_primed, never a silent execution as the device. Resolution is metadata equality only, so a printed username cannot impersonate anyone. See Scan codes.