Accepted adr-007 · 2026-10-23

A Principal's Id Has One Grammar, the Protocol's: PrincipalId in protocol.ncl, Refused Where a Principal Is Created

Context

Until 2026-10-23 a principal's id was any string: protocol.ncl typed Subject.id, Holders' holders and HatsOf's principal as String; signatiod's PutPrincipal and `signatio bootstrap` checked nothing; Cedar takes the id as a JSON string literal, so it takes anything. That a ULID or a short name worked was a fact of the code, not something a consumer could rely on.

A consumer measured the gap on 2026-10-22: wuwei-observer's view writes each act's Party as a reference `<kind>:<id>` and names its persons by a ULID that is to become the principal's name in PutPrincipal when the view links with the authority. It asked whether signatio or Cedar restrict anything a ULID does not meet. Nothing did — and nothing promised that nothing would. The principal decided to fix the grammar in the protocol.

Decision

A PRINCIPAL'S ID HAS ONE GRAMMAR, AND IT IS THE PROTOCOL'S. schemas/protocol.ncl publishes PrincipalId: 1 to 128 of A-Z a-z 0-9 . _ -, the first a letter or a digit (`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`). It types every place the protocol names a principal — Subject.id, Holders' holders, HatsOf's principal — and a consumer imports it as it imports the rest of the protocol (adr-001).

NO ':' AND NO '/'. A consumer's reference to a principal puts the kind before the id with ':' (`person:<id>`); a hat's and a document's ids join the project to the name with '/' (`<project>/<hat>`, cedar.rs). An id holding either could not be told apart from a reference or a scoped name.

REFUSED WHERE A PRINCIPAL IS CREATED. signatiod refuses PutPrincipal with an id outside the grammar, writing nothing; `signatio bootstrap` refuses its administrator before it opens the store. Every other op that names a principal (PutClient, Assign, SetKeyStatus, HatsOf) names one that must already exist.

THE KIND IS NOT IN THE ID. A principal's kind is its own field (PutPrincipal's `kind`, Subject's `kind`), and it never changes; a consumer that writes `person:<id>` maps it to `{ id, kind = Person }` and sends the bare id.

WHAT THIS DOES NOT DECIDE: a grammar for client ids, project ids or hat names; checking a Subject's id on the client side (signatio-attest, signatio-person); a migration of stores holding ids outside the grammar — the installed authority holds none, by the principal's statement of 2026-10-23, not by a measurement (adr-002 forbids reading the store).

Constraints

Alternatives considered