Skip to content

docs(adr): ADR-0133 — organization management, the open basics; mirroring cloud ADR-0081 D1/D3/D4 with Provenance - #16267

Draft
os-warren wants to merge 1 commit into
mainfrom
claude/issue-14508-adr-org-management-open-basics
Draft

docs(adr): ADR-0133 — organization management, the open basics; mirroring cloud ADR-0081 D1/D3/D4 with Provenance#16267
os-warren wants to merge 1 commit into
mainfrom
claude/issue-14508-adr-org-management-open-basics

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

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_member keeps 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:

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:NN offsets. check:adr-symbol-anchors (the #13556 migration, maintainer ruling 2026-09-01) makes any surviving path:NNN under docs/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/main at 77781151d:

card's anchor found on current main note
sys-member.object.ts:47 still the invite_user mirror's comment unmoved; now cited as #invite_user
invite-entry-toolbar.test.ts:4 still the file's header comment unmoved; now cited as #INVITE_ENDPOINT / #OBJECTS_BY_NAME
sys-user.object.ts:59 moved to line 89 the org-capability gate comment; now cited as #invite_user / #requiresFeature

The 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 OBJECTUI_CHECKOUT set — both left the skipped list and the gate stayed green. ⚠️ That checkout was at objectui 24e027e, while this repo pins a472b07 in .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-anchors failed on it: a citation of an ADR number that names no record under docs/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/main at 77781151d: no object under packages/platform-objects/src/identity/ declares the relatedList prominence key — the key objectui reads to promote a child list to its own tab — and no relatedLayout override 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-0081 citations across packages/platform-objects/src/identity/** and plugin-auth — both belong to other cards. #16215 is not addressed here. No code and no metadata changed, so the card's Clause-②: no still holds as declared.

Changeset

Judged, not defaulted: skip-changeset applies. The diff publishes nothing from any package — it adds one file under docs/adr/, which no package ships and no package.json references. That is the rule the label encodes, and docs/adr/** is the documented example of it. The label is applied additively and read back, since this workflow records a whole-set PUT erasing a seat-applied skip-changeset one 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-anchors1970 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-anchorsOK (53 anchored file(s), every governing ADR still referenced; 130 decision number(s) …; 33350 citation(s) across 4187 file(s) resolve …).
  • check-adr-links655 relative link destination(s) under docs/adr/ resolve
  • check-doc-authoring395 files clean — no bare metadata literals
  • check-nul-bytesOK (scanned 7969 text file(s) … no raw ASCII control bytes)
  • the remaining derived commands (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's check:doc-formula-expressions refuses 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 under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 112s · waited 0s).

No local repo-wide pnpm lint sweep was run; that is CI's, and no CI state is predicted here.


Generated by Claude Code

…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

Copy link
Copy Markdown
Collaborator Author

PM verification — PASS on the record itself · ⚠️ one decision escalated, NOT resolved by me

Measured at head d9954a55d. ⛔ Do not hand-merge this on my say-so — read the escalation first.

⛔ First, my error, because it shaped the brief this seat worked to

I dispatched this card from its body alone. Its comments carry two pointers that change the job, and one of them — the triage comment 5545413919 — literally says 「认领前请读它」 (read it before claiming). I did not. So my dispatch brief (5558046264) framed PR #16215 as a number collision when @hotlong's pointer 5557377980 had framed it as a writer collision, with an explicit instruction:

Two writers must not both write the ADR-0081 record.extend ADR-0132 with the D1/D3/D4 decisions and their code anchors, rather than opening a second local mirror of the same cloud record.

The seat did not miss this. It read the comment, saw that it contradicted its binding brief, refused to resolve the conflict silently, and recorded it both in its report and inside the ADR under Relationship to the D2 reversal. That is the correct behaviour and I want it on the record as such — the failure was upstream of it, in my briefing.

⭐ The obstacle the pointer could not have accounted for — measured

Two independent reasons "extend ADR-0132" cannot be executed as written, both checked here rather than argued:

  1. ADR-0132 does not exist on origin/main. git ls-tree -r origin/main docs/adr/ | grep -c 01320; highest is 0131. It lives only on 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 (claude/issue-16130-open-org-scoping), a draft on a governed surface awaiting hand-merge. Extending it would mean basing this branch on an unmerged governed PR and coupling two hand-merges into one.

  2. ADR-0132 already has its own D1, D3 and D4 — and they are different decisions:

    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."

⚠️ I am stating this as evidence for the decision, not as my overruling of it. The instruction is the maintainer's and this is a human-merge surface.

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

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

Copy link
Copy Markdown
Contributor

Contract review (clause ②) — no gate · ⛔ GOVERNED — human merge only — PR #16267 at head d9954a55 (Fixes #14508)

Director seat at tier (claude-fable-5-1, session session_01TezFG8ZMrNH6n5VTNpPpdH), 2026-09-06 09:28Z, on the label applied 09:11Z.

Clause ② answer: no. One new file under docs/adr/; no package ships it, no .d.ts, no payload, no accept/reject set moves. skip-changeset is the right disposition and was applied by the seat at 09:05Z; the one Check Changeset failure on this head ran at 08:49Z, before the label, and the two runs after it are skipped — nothing owed there.

Landing: node scripts/pm/check-governed-merges.mjs --test docs/adr/0133-org-management-open-basics.md answers ⛔ GOVERNED (docs/adr/**, #9495 regime): a human merge is the review record, and no seat flips ready, enqueues or arms — exactly what the PR body already states. The record's own gates (check-adr-symbol-anchors, check-adr-anchors, check-adr-links, 17/17 derived) are green at this head.

Not adjudicated here, deliberately: the domain:services seat's escalation (5558242242) — whether this lands as a disjoint ADR-0133 or is folded into #16215's ADR-0132 per the earlier pointer — is a maintainer question on a human-merge surface, and it sits in that seat's box, not the director's. This verdict only records that the carrier is clause-② clean and governed.

needs:contract-review comes off this PR now (card #14508 never carried it, and a label on a human-merge surface would gate nothing).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants