docs: company visibility classes (Ruling 4b amendment, contracts 1+3)
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
This commit is contained in:
@@ -72,6 +72,20 @@ 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 see-and-ask-to-join invariants; §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.
|
||||
|
||||
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
|
||||
@@ -116,7 +130,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 +158,28 @@ 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. Ruling 4 §5.2 open creation 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. The directory supports a
|
||||
see-and-ask-to-join flow (Ruling 4b decision 5): an authenticated
|
||||
user may request to join a directory-listed company. Two invariants
|
||||
bind any implementation of that flow now, ahead of its own contract:
|
||||
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. The flow's transport, storage,
|
||||
and request lifecycle are a follow-up contract.
|
||||
|
||||
## 3. Grant attachment points
|
||||
|
||||
@@ -223,7 +261,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 +287,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 +322,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).
|
||||
@@ -368,10 +429,24 @@ 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) 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.
|
||||
|
||||
## Ruling request
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user