docs: Amendment 1 rev 3 — terra round-2 fixes
ci/woodpecker/pr/ci Pipeline was successful

- user FKs text not uuid (users.id is BetterAuth text; matches
  hierarchy_grants shape)
- actor receipt split from canonical audit event: receipt carries
  {target id, old, new}; canonical event keeps hierarchy schema §5.2's
  full immutable snapshot; no audit read conferred; §8.6.3 witnesses
  both sides
- kanban SOT Amendment A3 (§10) expressly extends A2's sole-disclosure
  rule by exactly the capability-holder carve-out; ruling request names
  it
This commit is contained in:
fred
2026-08-29 14:04:12 -05:00
parent 99a0c89c5a
commit 6550bda5e1
2 changed files with 93 additions and 19 deletions
+44
View File
@@ -514,3 +514,47 @@ itself permits, so that contract does not stretch A1 by interpretation.
default-open disclosure. default-open disclosure.
5. Every other constraint of A1 — §8.1.2's remaining bullets, §8.2 in full, 5. Every other constraint of A1 — §8.1.2's remaining bullets, §8.2 in full,
and §8.3's other acceptance criteria — is untouched. and §8.3's other acceptance criteria — is untouched.
## 10. Amendment A3 — capability-holder existence disclosure
**Status:** amendment to Amendment A2, added by reviewed PR together with
contract 2 Amendment 1 (`rbac-grant-model.md` §8, this PR), under that
amendment's ruling request (decision owner Jason). It binds if and only if
contract 2 Amendment 1 ratifies; until then §9.1.2's sole-disclosure rule
stands unmodified — which is consistent, because until ratification the
company-CRUD capability class is empty and the carve-out below has no
holders. Everything in §§19 remains binding verbatim, with exactly the one
express modification below. The detailed contract text lives in
`rbac-grant-model.md` §8.1; this amendment changes only what A2 itself
permits, so that contract does not stretch A2 by interpretation.
### 10.1 What A3 modifies in A2
1. **Capability-holder disclosure (narrows §9.1.2's sole-disclosure rule
by one carve-out).** §9.1.2 makes the directory the sole permitted
existence disclosure and keeps private companies undisclosed to
non-granted subjects everywhere. A3 admits exactly one further
disclosure channel: a subject holding the company-CRUD capability
(contract 2 §8), when exercising the hierarchy schema §5.5 visibility
command, learns the target company's existence and its old/new
visibility values through the command's redacted actor receipt —
success for an existing target (private or directory alike) versus
`not_found` for a nonexistent id — bounded exactly as contract 2 §8.1
states: no name, slug, structure, content, grant, or membership
information, and no read command of any kind. To every other
non-granted subject, private companies remain undisclosed everywhere,
including the directory; the directory remains the sole
existence-disclosure _listing_.
### 10.2 What A3 explicitly does not change
1. The directory itself is unchanged: read-only, directory-class companies
only, existence/name/slug only (§9.1.2's enumeration is narrowed for
capability holders' receipts, widened for nothing).
2. No join-request surface, no curation listing, no read command of any
family is authorized (§9.2.2 unchanged; a curation listing is a further
amendment per contract 2 §8.1).
3. The canonical audit event for visibility mutations is untouched — it
keeps hierarchy schema §5.2's full immutable target snapshot; the
capability confers no audit read (contract 2 §8.5).
4. Every other constraint of A1 and A2 is untouched.
+49 -19
View File
@@ -30,7 +30,11 @@ anticipate. §8 defines the capability as a platform-scoped, admin-assigned,
audited delegation of exactly the hierarchy schema §5.5 company visibility audited delegation of exactly the hierarchy schema §5.5 company visibility
command — no read command, no other company operation; the mutation's command — no read command, no other company operation; the mutation's
inherent existence disclosure is ratified as a bounded carve-out to inherent existence disclosure is ratified as a bounded carve-out to
hierarchy schema §6.7/§2.8 (§8.1). The hierarchy role hierarchy schema §6.7/§2.8 and to kanban SOT Amendment A2's
sole-disclosure rule — SOT Amendment A3 (native-kanban-sot.md §10, this
PR) expressly extends A2 by exactly this carve-out (§8.1). The holder
sees only a redacted actor receipt; the canonical audit event keeps
hierarchy schema §5.2's full immutable snapshot. The hierarchy role
vocabulary (§2), every evaluation rule (§3), and grant management (§4) are vocabulary (§2), every evaluation rule (§3), and grant management (§4) are
untouched: the capability is not a `hierarchy_grants.role` value and untouched: the capability is not a `hierarchy_grants.role` value and
evaluates outside the chain; capability-row deletion joins §6.1's evaluates outside the chain; capability-row deletion joins §6.1's
@@ -292,15 +296,26 @@ further amendment, not an implementation decision.
(`not_found`), so a holder presenting candidate ids learns existence — (`not_found`), so a holder presenting candidate ids learns existence —
exactly as a platform admin already does through the same command. exactly as a platform admin already does through the same command.
This amendment ratifies that disclosure as part of the §5.5 curation This amendment ratifies that disclosure as part of the §5.5 curation
authority, bounded as follows: the mutation result and its audit event authority, bounded as follows. The holder-visible surface is the
disclose to the holder the target's existence and its old/new command's **actor receipt** — the mutation result payload, carrying
visibility values, and **nothing else** — no name, slug, structure, exactly the target id, old visibility, and new visibility, and
content, grant, or membership information. A successful publish **nothing else**: no name, slug, structure, content, grant, or
additionally makes the target directory-listed to every authenticated membership information. The actor receipt is a redacted projection
user; that is the command's ratified purpose (hierarchy schema §5.5), distinct from the **canonical audit event**, which is unchanged by
not a leak. Hierarchy schema §6.7's existence-oracle rule and §2.8's this amendment: it keeps hierarchy schema §5.2's deletion-safe
directory-only disclosure are amended by exactly this carve-out for immutable target snapshot (id, slug, and parent chain at event time)
capability holders and are otherwise untouched. Witnessed in §8.6.3. in full. The two never converge on the holder: the capability confers
no audit read (§8.5), so the canonical event — and with it the slug
and parent chain — is reachable only by subjects independently
authorized to read audit data, never through this capability. A
successful publish additionally makes the target directory-listed to
every authenticated user; that is the command's ratified purpose
(hierarchy schema §5.5), not a leak. Hierarchy schema §6.7's
existence-oracle rule and §2.8's directory-only disclosure are amended
by exactly this carve-out for capability holders, kanban SOT Amendment
A3 (native-kanban-sot.md §10, this PR) expressly extends A2's
sole-disclosure enumeration by the same carve-out, and all three are
otherwise untouched. Witnessed in §8.6.3.
2. **Holding: platform-scoped assignment, user subjects only.** The 2. **Holding: platform-scoped assignment, user subjects only.** The
capability is not a hierarchy grant: it attaches to no node, has no capability is not a hierarchy grant: it attaches to no node, has no
@@ -308,13 +323,19 @@ further amendment, not an implementation decision.
`platform_capabilities` table whose column set is exactly (nothing `platform_capabilities` table whose column set is exactly (nothing
else, per the contract 1 §2.7 exhaustiveness discipline): else, per the contract 1 §2.7 exhaustiveness discipline):
- `id` — uuid, primary key; - `id` — uuid, primary key;
- `user_id`uuid, NOT NULL, FK `users` **ON DELETE RESTRICT**; - `user_id`text, NOT NULL, FK `users` **ON DELETE RESTRICT**;
- `capability` — text, NOT NULL, constraint-checked against exactly - `capability` — text, NOT NULL, constraint-checked against exactly
`company_crud`; `company_crud`;
- `granted_by`uuid, NOT NULL, FK `users` **ON DELETE RESTRICT**; - `granted_by`text, NOT NULL, FK `users` **ON DELETE RESTRICT**;
- `created_at` — timestamptz, NOT NULL; - `created_at` — timestamptz, NOT NULL;
- UNIQUE (`user_id`, `capability`). - UNIQUE (`user_id`, `capability`).
The user FKs are **text**, not uuid, because `users.id` is a BetterAuth
text key (`packages/db/src/schema.ts`; custody schema records the same)
— PostgreSQL cannot reference a text primary key with a uuid column.
This matches the shipped `hierarchy_grants` shape exactly: uuid
surrogate `id`, text FKs to `users`.
Both user FKs are RESTRICT for the same reason contract 1 §3.3 pins Both user FKs are RESTRICT for the same reason contract 1 §3.3 pins
RESTRICT on principal FKs: the identity contract (§7.3) gates user RESTRICT on principal FKs: the identity contract (§7.3) gates user
deletion, and a cascade here could silently destroy a capability deletion, and a cascade here could silently destroy a capability
@@ -391,12 +412,19 @@ further amendment, not an implementation decision.
create behaves exactly as any active user's — always `private`, create behaves exactly as any active user's — always `private`,
and a creation request carrying a visibility argument is refused and a creation request carrying a visibility argument is refused
for holder and baseline alike (contract 3 §5.2). for holder and baseline alike (contract 3 §5.2).
3. Disclosure bound (§8.1 carve-out witnessed): the mutation result 3. Disclosure bound (§8.1 carve-out witnessed, receipt and canonical
for a private-valid target, a directory-valid target, and a event separately): the mutation result for a private-valid target,
nonexistent id is exactly {success, success, `not_found`}; no a directory-valid target, and a nonexistent id is exactly {success,
result or error payload carries name, slug, structure, content, success, `not_found`}; the actor receipt for a success carries
grant, or membership data; the holder's audit-visible surface exactly {target id, old visibility, new visibility} and no result
carries only existence and old/new visibility. or error payload carries name, slug, structure, content, grant, or
membership data; the canonical audit event for the same mutation —
asserted directly against the hierarchy outbox, not through any
holder-facing surface — carries hierarchy schema §5.2's full
immutable snapshot (id, slug, parent chain); and the holder's
attempt to read audit data is refused (no audit read conferred,
§8.5), proving the receipt/event separation reaches the holder as
a redaction, not a weakened event.
4. Assignment path, both polarities: a platform admin assigns and 4. Assignment path, both polarities: a platform admin assigns and
revokes through the normal admin command (positive witnesses — revokes through the normal admin command (positive witnesses —
assign then observe the §8.6.2 allow, revoke then observe deny); a assign then observe the §8.6.2 allow, revoke then observe deny); a
@@ -437,7 +465,9 @@ interpretive resolution named:
## Ruling request (Amendment 1) ## Ruling request (Amendment 1)
Ratify §8 and the Amendment 1 header note as written, with one decision Ratify §8, the Amendment 1 header note, and kanban SOT Amendment A3
(native-kanban-sot.md §10 — the express A2 carve-out extension, which
binds only with this ratification) as written, with one decision
embedded: embedded:
- Decision: the company-CRUD capability is a platform-scoped, - Decision: the company-CRUD capability is a platform-scoped,