Tags: FelineStateMachine/allons
Tags
0.2-c: local/auth — the vocabulary of identity The seven auth.* kinds were frozen at 0.1.0 and their payloads have never existed, so this is the release that puts those bytes in the field. Field numbers here are one-way doors under §10.2, which is why the five readings in 89d2a3e were settled first and why a sixth is recorded in ROADMAP with this commit. Pure types, codecs and functions: Role (values fixed by 0001_framework.sql:33), Actor, Policy/DefaultPolicy, Identity, Member, MembershipView, the capability cert with signing and verification, the invite format and its allons-invite:v1 codec, the seven payload codecs, and §9.3's conflict rules as pure functions. No SQL, no reducers, no I/O, and no import of local, local/sync or local/transport (DESIGN.md:162). Authorize returns three answers, not two. A failure that depends on the membership view quarantines; a failure that depends only on the operation's own bytes forbids. The zero Disposition is Quarantine, which is the same move Reducer.Apply's Touched makes for the same reason — the value you get by forgetting should be the one that is only costly, never wrong. There is deliberately no Authorized() bool: a boolean has two values, this has three, and the collapse a boolean invites ("not admitted, therefore refuse") is the bug that costs convergence. Genesis needs no special case. A self-signed grant routes to authorizeGenesis, which requires signature + seq == 1 + author == subject, so no caller ever asks "are there members yet". Field numbers chosen where DESIGN describes without spelling: Cert{workspace=1 ..signature=8}, signed body 1-7 because a signature cannot cover itself; ForkResolved{owner=1, chain_head=2}, losing branches omitted because a derivable field is one two implementations can derive differently; Invite = §9.5's seven in its order, then max_uses=8 (decision 4) and ticket=9. Both are covered by sig_by_admin, because the signed body is "every field but the signature" rather than "every field before it" — otherwise anyone relaying a link could raise the cap or redirect the joiner. Validated rather than accepted: I re-ran four of the agent's nine mutations myself and all four reproduce — the zero Decision, the genesis seq==1 condition, and dropping max_uses and ticket from the signed body, the last two failing with "editing max_uses left the invitation verifying". Reported diagnostics claiming undefined: InviteID and a grantOp arity mismatch were stale mid-edit snapshots; build, vet and tests are green. local/auth_vocabulary_test.go is mine, not the unit's. local/auth cannot import local, so the seven kind strings are spelled twice and nothing held the copies together. A divergence would not fail to compile: authorization would be checked for one string while dispatch used another, every replica would keep converging, and the operation would quietly stop being authorized. Mutated to confirm it fires. Nine ambiguities were flagged rather than silently resolved. Five are now rows in ROADMAP's carried-forward table: an enrollment cannot be verified by a peer that never saw the invitation (§9.5 says any peer can); workspace_id is documented as the hash of the genesis op and is not, which is what leaves a genesis grant unbound to the workspace it names; capability expiry has no renewal; a device record is asserted by its issuer rather than proved by the user key; and §3.3 makes an editor unable to rotate their own key. Not verified: nothing imports this package yet, so Authorize has never run against a real envelope, a real members row, or the drain. No wire-compat test against a third-party decoder, and §13.4's FuzzInviteParse is not written.