docs(adr): ADR-0133 — organization management, the open basics; mirroring cloud ADR-0081 D1/D3/D4 with Provenance - #16267
Conversation
…cloud ADR-0081 D1/D3/D4 Records, in the repository whose code enforces them, the open-mechanism half of cloud ADR-0081 (Accepted 2026-07-09): D1 teammate-add stays open and always goes through better-auth invitations, D3 the in-shell surface is the organization record page reached by a templated nav deep-link, D4 control-plane roster reads are organization-scoped. Decides nothing. D2, the non-goals and the commercial consequences stay in cloud and are cited as `cloud ADR-0081`. Where a local record already decides a point it is cited rather than duplicated: ADR-0093 (membership lifecycle, D9's active- organization resolution), ADR-0105 D12 (the multi-org entitlement anchor), and ADR-0131 D1/D7/D9 (total organization ownership), whose C6 census (#15207) — not this file — answers whether sys_member keeps its organization column. Every code anchor was re-located on origin/main at 7778115 and is written as a symbol anchor, since check:adr-symbol-anchors makes a line number a hard finding. The objectui rendering half is cited as a cross-repo anchor and is explicitly not claimed as this repository's contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
PM verification — PASS on the record itself ·
|
| ADR-0132's own | cloud ADR-0081's (this card's) | |
|---|---|---|
| D1 | The multi-organization runtime is open core | Adding a teammate is open, via better-auth invitations |
| D3 | One name, two packages; the declaring manifest decides which | The in-shell surface is the org RECORD page |
| D4 | The open package entitles both walled postures | Control-plane roster reads are org-scoped |
Folding this card's D1/D3/D4 into that file would give one record two different D1s, two different D3s and two different D4s. That is not a mirror a reader can check against its original — it is the opposite.
⭐ And ADR-0132's own body already scopes itself away from them: "D1, D3 and D4 of that record are untouched — see What this record does not decide."
The decision I am escalating
A — land 0133 as a disjoint record (what this PR does). The two files decide nothing in common, so folding them later is a move, not a rewrite.
B — hold this card until #16215 merges, then extend that file. Honours the pointer literally, at the cost of blocking a ready card on another seat's governed hand-merge for an unbounded time, and still leaves the duplicate-D-number problem to solve.
My reading is A, on the two measurements above. ⛔ But @hotlong wrote the pointer and filed the card; if the answer is B, say so and I will hold the card — the seat's work is not wasted, it becomes the extension text.
The record itself — verified
- One file,
docs/adr/0133-org-management-open-basics.md, +267. Nothing else in the diff. - ⭐ Zero line anchors.
grep -cE '\.ts:[0-9]+|\.json:[0-9]+'→ 0. This matters:check:adr-symbol-anchorsmakes apath:NNNanchor a hard finding underdocs/adr/**([finding] ADR line anchors intosql-driver.tshave rotted — 4 of 4 sampled resolve to unrelated code, and the ADRs read as if they still point at the mechanism they name #13556, maintainer ruling 2026-09-01, explicitly no transition period), so the card's own three offsets (sys-member.object.ts:47,invite-entry-toolbar.test.ts:4,sys-user.object.ts:59) could not be used at all. The seat pre-verified every replacement against the gate's own resolver and let it reject three candidates rather than shipping them. - Zero
ADR-0132citations — the first draft cited it by number,check:adr-anchorsfailed exit 1 because a number naming no record underdocs/adr/is a squat, and the seat replaced every token with a PR reference. ⭐ That is the same failure as this card's own, seen from the other side, and it caught it. - D-numbering preserved:
### D1,### D3,### D4with D2 visibly absent — cloud's numbering kept unrenumbered so the mirror lines up against its original. - Cross-check honoured, including the pointer I failed to relay: ADR-0093 D9, ADR-0105 D12 and ADR-0131 D1/D7/D9 are cited rather than restated, and ⛔ D4 does not decide whether
sys_memberkeeps its column — deferred to ADR-0131 D7's writer-facts question and the C6 census (feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207), exactly as 5536478826 required. - Number
0133derived from the tree and re-derived immediately before committing, checked against main, feat(organizations): bring the multi-organization runtime back to open core — the org-scoping registrar ships open, the licence gate stays in cloud (ADR-0132) #16215's branch, and all 40 open PRs. A number free onmainis not free if an open PR claims it.
Gates
17/17 green at the final head, family derived mechanically. One answered exit 3 = PREREQUISITE NOT MET (check:doc-formula-expressions, refusing to run against unbuilt output) — ⭐ cleared into a real measurement rather than reported as a pass, by building the two packages it names and re-running it green. The cross-repo objectui anchors are skipped-not-judged by default, so the gate was re-run with OBJECTUI_CHECKOUT set and both anchors left the skipped list — ⭐ skipped is not green, and the seat did not let it read as such.
Out of scope, filed not fixed
#16270 — "the org record page opens on tab-0 Members" is asserted in three places but declared by no metadata: zero relatedList prominence keys under packages/platform-objects/src/identity/, no relatedLayout override anywhere, so objectui's documented default collapses every related list into one stacked Related tab. The ADR therefore states the deep-link contract (which is declared) and does not assert a tab order (which is not). ⭐ The issue deliberately does not pick between "metadata missing a declaration" and "three documents stale" — telling them apart needs the running app. Correct call; declaring the key here would have been a metadata change the Clause-②: no forbids.
Status: Clause-②: no holds as declared — no code, no metadata. Draft, ready never flipped, auto-merge never armed, skip-changeset judged rather than defaulted. ⛔ Governed surface: human merge, and the A/B question above is owed an answer first.
Generated by Claude Code
Contract review (clause ②) — no gate · ⛔ GOVERNED — human merge only — PR #16267 at head
|
Fixes #14508
⛔ DRAFT, and it stays draft. The diff touches
docs/adr/**, a governed surface: the maintainer hand-merges. Do not flip ready, do not arm auto-merge.One new file,
docs/adr/0133-org-management-open-basics.md. Nothing else in the tree changes.What it records
The open-mechanism half of cloud ADR-0081 (Accepted, founder-decided in session, 2026-07-09), stated as this repository enforces it — D1 (adding a teammate is open and always goes through better-auth invitations), D3 (the in-shell surface is the organization record page behind a templated nav deep-link) and D4 (control-plane roster reads are organization-scoped).
⛔ It decides nothing. D2, the non-goals and the commercial consequences stay in cloud and are cited as
cloud ADR-0081. The cloud D-numbers are kept unrenumbered — a mirror a reader cannot line up against its original is not checkable — so the sequence reads D1, D3, D4 with D2 visibly absent.Where a local record already decides a point, the file cites it instead of restating it: ADR-0093 (membership lifecycle; its D9 already anchors the active-organization resolution the code cites as "ADR-0081 D1"), ADR-0105 D12 (the multi-org entitlement anchor), and ADR-0131 D1/D7/D9 (total organization ownership). Per the card's later pointer comment, D4 does not decide whether
sys_memberkeeps its organization column — that is ADR-0131 D7's writer-facts question, answered by the C6 census in #15207, which the file cites and leaves alone.The ADR number: 0133, and what it was checked against
Three independent checks, the last re-run immediately before the commit:
ls docs/adr/onorigin/mainat77781151d— the highest record is 0131.claude/issue-16130-open-org-scoping) addsdocs/adr/0132-multi-organization-runtime-is-open-core.md, so 0132 is claimed by an open PR even though it is free onmain.⇒ 0133. If #16215 does not merge, this leaves a gap at 0132; a gap is the cheap outcome and a collision is the expensive one.
⭐ Two findings that changed how the file is written
1. A line-number anchor in an ADR is now a hard gate failure. The card supplied three
file.ts:NNoffsets.check:adr-symbol-anchors(the #13556 migration, maintainer ruling 2026-09-01) makes any survivingpath:NNNunderdocs/adr/**a finding with no transition period — the census behind it measured 243 of 337 line anchors already broken. So every anchor in this file is written as a symbol anchor (path#symbol) or a file-level path, and each was verified against the gate's own resolver before the file was written.Re-locating the card's three anchors on
origin/mainat77781151d:sys-member.object.ts:47invite_usermirror's comment#invite_userinvite-entry-toolbar.test.ts:4#INVITE_ENDPOINT/#OBJECTS_BY_NAMEsys-user.object.ts:59#invite_user/#requiresFeatureThe objectui rendering half is cited as cross-repo anchors. These are reported-not-judged unless a checkout is supplied, so they were additionally verified by re-running the gate with⚠️ That checkout was at objectui
OBJECTUI_CHECKOUTset — both left the skipped list and the gate stayed green.24e027e, while this repo pinsa472b07in.objectui-sha; the symbols are stable declarations, but the reading is stated rather than implied.2. The unmerged record's number could not be cited at all. The file initially cited ADR-0132 by number for the D2 reversal.
check:adr-anchorsfailed on it: a citation of an ADR number that names no record underdocs/adr/is a squat, and every such citation is retroactively falsified if the record lands under a different number. The reversal is therefore referenced by PR number throughout, with the reason recorded in the file so the next author does not undo it. When #16215 merges, its number becomes citable and the file should be updated to use it.Open question left for the hand-merge, not resolved here
⭐ "Opens on tab-0 Members" is asserted in this repo's comments but declared by none of its metadata. Measured on
origin/mainat77781151d: no object underpackages/platform-objects/src/identity/declares therelatedListprominence key — the key objectui reads to promote a child list to its own tab — and norelatedLayoutoverride exists anywhere in this repository. Under the documented default with no primary list declared, related lists collapse into a single stacked tab. Two source comments and a QA checklist item nevertheless describe a Members/Invitations/Teams tab strip.So the ADR states the deep-link contract, which is declared, and does not assert a tab order, which is not — and records the discrepancy under "What this record does not decide" rather than repairing it. Filed separately; ⛔ not fixed in this PR.
Scope held
⛔ Not touched, deliberately: the cloud-repo pointer comment on cloud ADR-0081, and #14361's re-pointing of the bare
ADR-0081citations acrosspackages/platform-objects/src/identity/**andplugin-auth— both belong to other cards. #16215 is not addressed here. No code and no metadata changed, so the card'sClause-②: nostill holds as declared.Changeset
Judged, not defaulted:
skip-changesetapplies. The diff publishes nothing from any package — it adds one file underdocs/adr/, which no package ships and nopackage.jsonreferences. That is the rule the label encodes, anddocs/adr/**is the documented example of it. The label is applied additively and read back, since this workflow records a whole-setPUTerasing a seat-appliedskip-changesetone second after it was written.Verification
Gate set derived mechanically on the final commit —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, which reports its own provenance (derived from the tree of 'objectstack-ai/objectstack' at commit d9954a55d) and takes its change set from the merge base itself rather than a hand-built diff.17 commands derived, 17 green at
d9954a55d. Each exit code was captured immediately after a single redirected command, never through a pipe. Verdict lines are the gates' own:check-adr-symbol-anchors— 1970 anchors across 136 records resolve — 218 symbol (195 declaration, 23 literal), 1729 file-level, 23 cross-repo, 6 exempt, 3 continuation. 0 line anchors survive.check-adr-anchors— OK (53 anchored file(s), every governing ADR still referenced; 130 decision number(s) …; 33350 citation(s) across 4187 file(s) resolve …).check-adr-links— 655 relative link destination(s) under docs/adr/ resolvecheck-doc-authoring— 395 files clean — no bare metadata literalscheck-nul-bytes— OK (scanned 7969 text file(s) … no raw ASCII control bytes)check-ci-filter-parity,check-closing-keyword-parity,check-comment-mask-corpus,check:cross-package-test-inputs,check:driver-memory-census,check:pm-governed-merges,check:refd-timer-probe,check:watch-hint-literal, and the four self-test halves) all exit 0.One gate first answered exit 3 — PREREQUISITE NOT MET, which is not a pass and is not a finding:
@objectstack/lint'scheck:doc-formula-expressionsrefuses to run against unbuilt workspace output. It was cleared rather than reported, by building the two packages it names through the shared verify lock, and then re-run: 22 record-scoped formula example(s) across 430 files / 1371 TS blocks judged clean, plus its spec-TSDoc and field-level legs. The build ran underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0 · held the lock 112s · waited 0s).No local repo-wide
pnpm lintsweep was run; that is CI's, and no CI state is predicted here.Generated by Claude Code