From bd749831b16bfea78b578bcabc27f440388f162a Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 28 Aug 2026 20:17:35 +0000 Subject: [PATCH] docs: company visibility classes (Ruling 4b amendment, contracts 1+3) (#1461) --- docs/requirements/hierarchy-schema.md | 122 +++++++++++++++++++++++-- docs/requirements/native-kanban-sot.md | 58 ++++++++++++ docs/requirements/onboarding-wizard.md | 25 ++++- 3 files changed, 192 insertions(+), 13 deletions(-) diff --git a/docs/requirements/hierarchy-schema.md b/docs/requirements/hierarchy-schema.md index 5287133d..e5525bbf 100644 --- a/docs/requirements/hierarchy-schema.md +++ b/docs/requirements/hierarchy-schema.md @@ -72,6 +72,25 @@ example now lists the complete measured set, and the import analysis is extended to resolve literal dynamic `import()` routes, which two of the four members use. +Amendment 1 (Ruling 4b, 2026-08-28): company visibility classes. The +directory exists so one shared company can serve many users instead of +each user creating a duplicate private company (Ruling 4b, webui-audit +lane, ruled 2026-08-27). §2.1 gains a `visibility` column; §2.8 defines +the two classes (`private`/`directory`), the directory's existence-only +disclosure, and the pre-binding invariants for the deferred +see-and-ask-to-join flow (no join-request surface is authorized here — +its flow is a follow-up contract); §5.2's mutation +enumeration gains the visibility change; §5.5 defines who may change +visibility (platform admins, plus a company-CRUD capability whose +definition is a follow-up amendment to contract 2 — until it ratifies, +admin-only); §6.1 and §6.9 add the witnesses; §6.7's existence-oracle +rule is scoped around the ratified directory carve-out. Top-level +creation (contract 3 §5.2) is unchanged and always yields a private +company. Upstream, SOT Amendment A2 (native-kanban-sot.md §9, this PR) +expressly extends A1 §8.1.2 to admit the visibility column and A1 +§8.1.3 to admit the directory function — this contract relies on that +amendment, not on a reinterpretation of A1. + Scope: the tenancy/authorization structure record class — companies, estates, platform-projects, workspaces, hierarchy grants, their parentage, and constraints. Out of scope: the RBAC grant vocabulary and evaluation @@ -86,8 +105,13 @@ legacy flat data (future work; see §1.3). AND `hierarchy_grants` (§3) — A1 includes hierarchy-level access grants in the class. Every rule addressed to "the class" in this contract (payload prohibition, mutation path, audit) binds all five tables. Class - rows carry parentage, naming, grant, and audit-linkage data only — never - task, plan, or any business/orchestration payload. + rows carry parentage, naming, grant, audit-linkage, and visibility-class + data only — never task, plan, or any business/orchestration payload. + Visibility (`companies.visibility`, §2.8) is admitted into that + enumeration by SOT Amendment A2 §9.1.1, which expressly extends A1 + §8.1.2 for exactly this one column: it is disclosure data about the + class's own nodes — not a payload field, carries no business content, + and widens the payload prohibition for nothing else. References from business/orchestration rows into the class are limited to exactly one form: the canonical `workspace_id` tenancy column that REQ-TEN-001 requires on every canonical row, referencing @@ -116,7 +140,9 @@ MUST NOT merge the two. (A rename of either remains an implementation-PR decision under A1; this contract pins only that they stay distinct tables.) 1. `companies` — id (uuid pk), name, slug (unique per deployment), - created_at, updated_at. N per deployment (D2). + `visibility` (text NOT NULL, DEFAULT `private`, CHECK constrained to + exactly `private` | `directory`; semantics §2.8), created_at, + updated_at. N per deployment (D2). 2. `estates` — id, name, slug, `company_id` NOT NULL → `companies.id` ON DELETE RESTRICT. Exactly one company per estate; a company holds any number of estates. @@ -142,6 +168,31 @@ decision under A1; this contract pins only that they stay distinct tables.) free-form payload field. The columns declared in this section and §3 are exhaustive: a class table's column set is exactly its declared set (verified per §6.2) — nothing else (A1 §8.1.2). +8. **Company visibility classes (Ruling 4b).** Every company is exactly + one of two classes, carried by `visibility`: + - `private` (the default): the company is disclosed only to subjects + holding a grant on it or on a descendant — the resting state every + company is created in. Open creation under contract 3 §5.2 + (Ruling 4) survives unchanged: it creates private companies. + - `directory`: the company is listed in the deployment-wide company + directory. Directory listing discloses **existence, name, and slug + to every authenticated user — nothing else**: no subtree structure, + no roll-up aggregates, no workspace content, no grant or membership + information. + Visibility is disclosure, not authority. Content and structure access + to a directory-listed company still require explicit grants — + contract 2 §3.1 deny-by-default is unchanged, and the ownership model + (§4.4, contract 2 §4.3) is unchanged. Ruling 4b decision 5 wants a + see-and-ask-to-join flow for directory-listed companies. **This + contract authorizes no join-request runtime surface**: the flow in + its entirety — the ability to submit a request, its transport, + storage, and request lifecycle — is a follow-up contract, and until + that contract ratifies, the directory's only function is the + read-only listing above (A2 §9.1.2 admits nothing more). Two + invariants pre-bind that future contract now: + a join request confers no authority of any kind, and approval is + ordinary grant creation by an effective `owner` under contract 2 §4.1 + — there is no other acceptance path. ## 3. Grant attachment points @@ -223,7 +274,8 @@ shape contract 2 attaches to: 2. **Audit parity.** A1 §8.2 leaves every pre-existing REQ binding, so hierarchy mutations get REQ-AUD-001's guarantees, not a weakened substitute. Concretely: - - Every create, rename, transfer, grant create/change/revoke, and + - Every create, rename, transfer, visibility change (§5.5), grant + create/change/revoke, and delete — including every grant deletion cascaded by a node delete — emits a semantic audit event carrying actor, verb, target, and (for transfers) source and destination parents, with the correlation, @@ -248,6 +300,25 @@ shape contract 2 attaches to: workspace work. Contract 8 owns projection details but cannot narrow this rule. This contract additionally guarantees the chain roll-ups aggregate over is unique and non-null (§2.5). +5. **Visibility administration (Ruling 4b decisions 2–3).** Changing + `companies.visibility` is a hierarchy mutation through the §5.1 + command path, audited per §5.2 (the event carries the old and new + visibility values as its semantic content). It is authorized for + exactly two actor classes: platform admins (`users.role = 'admin'`) + and subjects holding the company-CRUD capability that a follow-up + amendment to contract 2 will define — until that amendment ratifies, + the capability class is empty and the command is admin-only. + A company `owner` as such may NOT change visibility: standard users + cannot publish a company into the directory. This is the one + hierarchy mutation a platform admin performs without holding a + hierarchy grant, and it is ratified here as instance administration + (directory curation) in contract 2 §1.1's sense, not tenant access: + the command mutates the single `visibility` column, reads no tenant + content, and confers no grant — contract 2 §1.1's + no-implicit-tenant-access rule is otherwise untouched. Top-level + company creation (contract 3 §5.2) always creates + `visibility = 'private'`; the creation command cannot set or change + visibility. ## 6. Verification requirements @@ -264,7 +335,10 @@ Binding on the implementing PRs (extends A1 §8.3): witnessed (zero and two set → refused). Grant uniqueness: a duplicate (subject, target, role) row refused for each of the six subject×target forms, proving NULLS-NOT-DISTINCT semantics; NOT NULL on `role`, - `granted_by`, and all `name`/`slug` columns witnessed. + `granted_by`, and all `name`/`slug` columns witnessed. Company + visibility (§2.8): a value outside `private`/`directory` refused with + both valid values accepted as the control; an insert omitting the + column defaults to `private`. 2. Column allowlist: an information_schema assertion that each class table's column set is exactly the set declared in §2/§3 — the bounded observable for no-payload (§2.7) and no-`owner_id` (§4.4). @@ -346,9 +420,19 @@ Binding on the implementing PRs (extends A1 §8.3): static analysis cannot see, and any such evasion found later is corrected as a conformance defect, not grandfathered. 4. Audit witnesses: for each mutation class (create, rename, transfer, - grant create/change/revoke, delete) — the event exists after commit - with actor/verb/target and same-transaction atomicity; a rolled-back - mutation leaves no event (rollback witness); a node delete's cascaded + visibility change, grant create/change/revoke, delete) — the event + exists after commit + with actor/verb/target and same-transaction atomicity, and the + event's outbox record exists after the same commit — state row, + audit event, and outbox record are witnessed as one transaction + (REQ-AUD-001); a rolled-back + mutation leaves no event, no outbox record, AND no state effect — + a rolled-back create leaves no row, a rolled-back rename, transfer, + or visibility change leaves the prior values in place, and a + rolled-back delete or grant revoke leaves the row present + (rollback witness on all three legs, per REQ-AUD-001's + commit-or-roll-back-together acceptance); a + node delete's cascaded grant deletions are each covered by events; events survive deletion of their target (query the events of a deleted node). 5. Transfer tests: parent-FK update moves the subtree resolution and @@ -368,10 +452,30 @@ Binding on the implementing PRs (extends A1 §8.3): endpoints mutate no canonical state anywhere (assert zero writes across hierarchy AND workspace tables, not hierarchy only); readers see aggregates only over workspaces they are authorized on, with no - cross-tenant existence oracles (A1 §8.3 acceptance 3). + cross-tenant existence oracles (A1 §8.3 acceptance 3, as narrowed by + A2 §9.1.2) beyond the one + ratified carve-out — the §2.8 company directory, witnessed in §6.9. 8. Real-PostgreSQL coverage for every constraint witness (unique/CHECK/ RESTRICT/NULLS NOT DISTINCT behavior), using the `ci-postgres` service in the `test` CI step; mocked specs cannot witness database constraints. +9. Visibility witnesses (§2.8, §5.5): the directory read returns exactly + the `visibility = 'directory'` companies to any authenticated user, + disclosing existence, name, and slug only (closed-field assertion on + the response shape); a private company never appears in the directory + for a reader without a grant on it (with the control: it appears in + that reader's granted-structure reads); a directory-listed company's + subtree, aggregates, and content remain refused for a non-granted + reader (disclosure ≠ authority); the visibility command is refused + for a non-admin actor — including an effective `owner` of the target + company — with the platform-admin accept control; top-level creation + yields `visibility = 'private'` and accepts no visibility argument; + each visibility change emits its §5.2 audit event carrying old and + new values — the full audit pattern for the mutation class + (same-transaction atomicity of state row, audit event, and outbox + record; rollback leaving no state effect, no event, and no outbox + record; actor/verb/target) is §6.4's, which enumerates + visibility change; this item adds only the old/new-value payload + assertion. ## Ruling request diff --git a/docs/requirements/native-kanban-sot.md b/docs/requirements/native-kanban-sot.md index 9ba524fe..ea99a50b 100644 --- a/docs/requirements/native-kanban-sot.md +++ b/docs/requirements/native-kanban-sot.md @@ -456,3 +456,61 @@ this line is weakened. - Negative tests prove roll-up endpoints cannot mutate state and that a reader sees aggregates only over workspaces they are authorized on (no cross-tenant existence oracles). + +## 9. Amendment A2 — company visibility classes and the company directory + +**Status:** amendment to Amendment A1, added by reviewed PR under Ruling 4b +(operator ruling, 2026-08-27; decision owner Jason; recorded in the webui-audit +lane RULINGS.md). Everything in §§1–8 remains binding verbatim, with exactly +the two express modifications below. Nothing else is weakened. The detailed +contract text lives in the hierarchy schema contract +(`hierarchy-schema.md` §2.8, §5.5, §6.9); this amendment changes only what A1 +itself permits, so that contract does not stretch A1 by interpretation. + +### 9.1 What A2 modifies in A1 + +1. **Class data (extends §8.1.2's first constraint).** The tenancy/authorization + structure record class additionally carries **visibility-class data**: the + single column `companies.visibility`, values `private` | `directory` + (hierarchy schema §2.8). Visibility is disclosure data about the class's own + nodes — what a company row reveals about its own existence — and is part of + the class's tenancy/authorization purpose. It is not business or + orchestration payload. §8.1.2's payload prohibition is widened for nothing + else: hierarchy tables still MUST NOT carry task, plan, or any other + business/orchestration payload, and this amendment admits exactly this one + column. +2. **The company directory (extends §8.1.3's function enumeration).** The + hierarchy serves one additional, express, narrow runtime function: the + **company directory** — a read-only disclosure listing of exactly the + companies whose `visibility = 'directory'`, revealing existence, name, and + slug to every authenticated user of the deployment and nothing else. It + mutates nothing, confers no authority, evaluates no grant down the chain, + and aggregates nothing (it is not a roll-up). §8.3's + no-cross-tenant-existence-oracle acceptance is narrowed by exactly this one + ratified carve-out: the directory is the sole permitted existence + disclosure, and it discloses only directory-class companies (witnessed in + hierarchy schema §6.7 and §6.9). Private companies remain undisclosed to + non-granted subjects everywhere, including the directory. + +### 9.2 What A2 explicitly does not change + +1. Content access stays grant-only under the RBAC grant model contract: + directory listing discloses existence, never content, membership, or any + authority (Ruling 3 unchanged; hierarchy schema §2.8). +2. **No join-request surface is authorized.** Ruling 4b decision 5's + see-and-ask-to-join flow is a follow-up contract in its entirety — + including the ability to submit a request. A2 admits exactly the + read-only listing of §9.1.2 and nothing more; hierarchy schema §2.8 + states the invariants that pre-bind the future flow contract, and that + contract must itself amend this enumeration before any join-request + runtime surface exists. +3. Visibility changes are hierarchy mutations on the existing §8.2.3 audited + mutation path — audited maintenance of the class's own structure in + §8.1.3's sense, not a further runtime function. Authorization for them is + defined in hierarchy schema §5.5 (platform admins plus the future + company-CRUD capability; owner-as-such cannot publish). +4. Company creation is unchanged and always yields `visibility = 'private'` + (onboarding wizard §5.2); this amendment adds no creation path and no + default-open disclosure. +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. diff --git a/docs/requirements/onboarding-wizard.md b/docs/requirements/onboarding-wizard.md index 67153aa2..b50fb8e4 100644 --- a/docs/requirements/onboarding-wizard.md +++ b/docs/requirements/onboarding-wizard.md @@ -397,6 +397,14 @@ seed-workspace-scoped mutant, correctly refusing outside the seed set, passes branch (c), so the two branches detect distinct mutants. No other change. +Amendment 1 (Ruling 4b, 2026-08-28): §5.2's embedded decision was RULED +AGREED (Jason, 2026-08-27), and Ruling 4b adds company visibility +classes (hierarchy schema §2.8): open top-level creation always yields +a **private** company; publishing a company into the deployment-wide +directory is a separate, gated visibility mutation (hierarchy schema +§5.5) that is never part of the creation command. §5.2 is amended to +state both. + Scope: the Gateway-backed product onboarding wizard. Out of scope: the host-local install wizard (`mosaic wizard`, which drives host install and gateway bootstrap and is not this artifact — audit REPORT.md layer 3); @@ -1169,15 +1177,18 @@ collects no sensitive category, so v1 ships no custody surface. party, service actor, or wizard-privileged writer exists in this flow. 2. **Post-bootstrap top-level company creation** — the "N companies" flow - — is decided by the ruling below: any **eligible platform user** MAY + — RULED AGREED (Jason, 2026-08-27): any **eligible platform user** MAY create a top-level company and MUST name an initial `owner` grant in the same audited operation (contract 2 §4.3); the creator naming themselves is the default. Eligible means, in identity-contract terms: an authenticated account (identity §2) that is not banned (identity §7.1 — deactivation on this platform IS the better-auth ban; no separate deactivated state exists). No further role or grant - is required. Until that ruling, deny-by-default holds (contract 2 - §3.1): no implicit creation authority exists. + is required. Creation always yields a **private** company + (`visibility = 'private'`, hierarchy schema §2.8, Ruling 4b): the + creation command accepts no visibility argument, and publishing into + the deployment-wide directory is a separate, gated mutation + (hierarchy schema §5.5) that standard users cannot perform. 3. Child-node creation inside the wizard (estate, project, workspace under the seeded company) follows contract 2 §4.3: parent `owner` authority, no automatic grant needed — for canonical seed @@ -1932,7 +1943,13 @@ contracts and are not additions: suffix at all — each contradicting PRD D4's no-lock-in requirement (§4.4). -## Ruling request +## Ruling request — RULED AGREED (Jason, 2026-08-27; Amendment 1) + +The §5.2 decision below was ruled agreed: open eligible-user creation +stands (yielding private companies per Amendment 1), and the +"alternative if rejected" did not take effect. The request is retained +below as historical record of what was put to ruling; it is no longer +live. Ratify sections 1–7 as written, with one decision embedded: