oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1# Roster producer storage integration contract
2
3Scope: discussion 33, implementing the approved roster contract in discussion 28.
4This branch adds no mounted schema, route, operator command or production data.
5
6`directorydata.RosterSchema` follows `directorydata.Schema` in a future directory
7schema merge. It creates empty generation, consumer, token and receipt tables.
8`NewRosterSource(db, sealer, directoryConfig, directoryOrigin)` copies the existing
9application and route policy. `Read(ctx, RosterRequest, now)` returns the shared
10`roster.Page`, HTTP status and a fixed error. A successful 409 has a nil error and
11no member fields. The HTTP caller must sign both 200 and 409 with shared
12`roster.SignPage`; no raw Page is a trusted wire response.
13
14The HTTP integration still owns exact Host enforcement, method/query/header
15shape and length checks, no-store on every response, fixed response bodies,
16no request-URL logging, and roster-only signing keys. `Read` has no outbound
17transport. All scope comes from the token's consumer and its matching live
18registry row. A requested team only compares against that scope.
19
20## Future audited writer transaction
21
22There is deliberately no public mutation API. The following are preconditions
23for the root-owned operator integration, not authority conferred by these tables.
24Before opening its write transaction, authenticate and authorize the operation
25using the separately reviewed operator authority. Verify the operation ID,
26monotonic record sequence within its consumer scope, exact directory, consumer, team, app, audience and
27expected current generation. Refuse replay, changed payload under an old ID,
28and unknown authority. Never accept browser identity or bearer possession as
29operator authority. Recheck expected stored state under the transaction lock.
30
31Within the same transaction, write the intended registry/consumer/token or
32generation change and its successful receipt. The receipt retains only opaque
33operation ID, sequence, fixed operation kind, optional consumer ID, generation,
34SHA256 record digest and completion time. It is not a signed provisioning record
35and cannot approve a client generation. The operator path must deliver the
36separately signed record through the approved transport. No token or team name
37belongs in the receipt. Name sealing remains the existing ID-bound helper.
38
39Token slots are normalized rows with a globally unique SHA256 hash of the exact
40canonical 43-character bearer string. This prevents cross-consumer and cross-slot
41collisions. To rotate, delete an older previous slot, change the current row to
42previous with issued_at equal to the rotation time and expires_at at most 300
43seconds later, and insert the new current hash. Here issued_at starts the slot's
44validity window; it is not the original token creation timestamp. Current slots
45have no expiry. Future-dated slots fail. Disable by setting enabled=0 in the
46same audited transaction; no read changes slots or extends an overlap.
47
48Ordinary boot never fills generation. Bootstrap needs the independently approved
49fresh-install path. Restore needs the platform's fenced maintenance path and a
50new random generation plus newly approved records before traffic resumes.
51Directory storage alone does not supply either path. Generation and receipt
52values must be canonical and exact when written; the reader also rejects a
53malformed generation. Consumers bind to a matching registry consumer/team/app/
54origin and the injected app/route policy; pending or disabled instances do not
55serve rosters.
56
57Membership INSERT, UPDATE and DELETE triggers advance affected teams' epochs in
58the writing transaction and abort on signed 64-bit overflow. The future writer
59must not add a second manual increment. Moving membership bumps both teams once;
60changing a role bumps one. Even a no-op UPDATE bumps its team. Do not lower an
61epoch or bypass triggers. Restore is the separate generation-fenced procedure.
62
63## Snapshot and cursor
64
65One read transaction resolves the token, enabled/overlap state, live binding,
66generation, epoch, count and ordered page. It does not keep a server snapshot
67between requests. A membership write after that transaction begins may appear
68on the next call; the consumer's standing lease accounts for this bound.
69
70The cursor is sealed with purpose `directory.roster-cursor/<directory origin>`.
71Its closed JSON binds consumer, team, generation, epoch, fetch challenge,
72last opaque subject, next index, issuance and expiry. It expires at ten seconds,
73including at the exact boundary. Wrong scope/challenge, malformed, expired or
74unauthentic cursors return fixed 400. A scoped valid cursor with changed
75generation/epoch returns the signed 409 shape with current values. Rotation and
76disable are checked again before cursor processing. No cursor grants authority.
77