Repository navigation
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
|
…aimed by an open PR) `0132` is free on `main` but claimed on the branch of an open PR — #16215 adds `docs/adr/0132-multi-organization-runtime-is-open-core.md`. Verified here by a full `git diff --name-only origin/main...<pr-head> -- docs/adr/` over all 27 open PRs, not by titles: 0132 is #16215's, 0133 is #16267's, 0134 is #16476's, and 0135 is unclaimed. File renamed with `git mv`, slug unchanged. The record's one self-citation (its title line) moves with it; the `ADR-0105` and `ADR-0108` references are landed records and are untouched. No line-number anchors were introduced — the file still carries none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…r-org-management-open-basics
The record that reverses cloud ADR-0081 D2 has landed on `main` as ADR-0132, so this file's account of it is stale: it described that record as an open draft PR, cited it by PR number because an unmerged ADR number was a squat, and still said D2's packaging decision "stays in cloud". - header bullet — D2 is not mirrored because it has been REVERSED; what stays in cloud is the record, not the machinery, which now ships in this repo - "Relationship to the D2 reversal" heading and body — cite ADR-0132 by number and relative link; the PR-number citation the anchors gate used to force is retired, and the in-file link target moves with the heading - division-of-labour table — the D2 row points at ADR-0132 - the note for the hand-merge — folding the two records into one was blocked by ADR-0132 being an unmerged governed PR; that obstacle is gone, and this change still does not take the option, which stays a merge-time call - "What this record does not decide" item 4 — same re-pointing The file wrote its own trigger for this edit, and the condition is now met. Nothing about the reversal is re-derived, restated or evaluated here: ADR-0132 records that it does not touch cloud ADR-0081 D1/D3/D4, which are this file's whole content, so the two remain disjoint. `origin/main` is merged in so the cited record exists in the tree the gates read. Gates: check-adr-anchors exit 0; check-adr-symbol-anchors exit 0, verdict "0 line anchors survive"; check-governed-merges --test exit 3 GOVERNED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
The hand-merge note said the only thing blocking a single combined record was ADR-0132 being unmerged, and that with it landed the choice "stands on its merits alone". True, but it leaves the reader to rediscover the merit that matters: that record carries its own D1 through D6, which are different decisions from cloud ADR-0081's D1/D3/D4, so folding this mirror into it puts two unrelated D1s in one document — exactly the property the deliberately unrenumbered mirror exists to keep checkable. Stated, not decided: the disposition is still the maintainer's merge-time call, and this change takes the option neither way. Gates re-run on this tree: check-adr-anchors exit 0, check-adr-symbol-anchors exit 0 with "0 line anchors survive", check-adr-links exit 0, check-nul-bytes exit 0, check-doc-authoring exit 0, check-governed-merges --test exit 3 GOVERNED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
PM 独立复核 —— PASS。依赖已落地的更新到位,红也没了复核在独立 worktree 里 checkout 到 head 1. 那条红:陈旧读数,已消失
新 head 上实测:33 个 check run,0 个 failure。 2. 门禁,PM 自跑
3. 交付面
4. 席位声明的偏离 —— 接受它把 ⭐ 而且它给的理由成立且是我没预见到的: 5. ⭐ 席位多做的一处,是对的我列了 5 处要改,它改了 6 处(小节第二段是独立站点,同时承载「按 PR 号引用」的规则与本文件自己的更新触发条件),并从卡的评论历史里读出了第 7 处:
⇒ 之前「不能并进 0132」的理由是它还没合并;那个障碍随合并消失了,但另有一个结构性障碍幸存。文件把它陈述出来而不裁决,并明说合并时若维护者要合成一份,那是 merge-time 的决定、两份不相交、是移动不是重写。⭐ 这正是正确姿态:⛔ 席位不替维护者选。 6. 只报不改的两条
⛔ 保持 draft,等人工合并。 PM 未 flip ready、未 arm auto-merge、未 Generated by Claude Code |
Escalation answered by the maintainer's own act — option A, land ADR-0133 as a disjoint record (director seat, 2026-09-07)The A/B question in the PM verification comment (5558242242) — land Recorded so the earlier pointer ("extend ADR-0132") is read as superseded by measurement (ADR-0132's own D1/D3/D4 are different decisions) and by this ruling, not silently dropped. Generated by Claude Code |
…y & access architecture (objectstack-ai#16478) * docs(adr): ADR-0132 mirrors the open half of cloud ADR-0024 — identity & access architecture Records, in this repository, the mechanism half of `objectstack-ai/cloud` ADR-0024 (Accepted there 2026-06-25) whose code lives here: per-environment identity (D1), the two user populations (D2), the managed-vs-env-native source-of-truth marking (D4), identity-from-the-IdP with authorization decided in the environment (D5) and the SSO-era local user-management surface (D5.2), per-environment SSO with opt-in domain verification (D6), preserved portability and self-host (D7), and env-local users with better-auth-backed organization membership (D9). Every clause carries a symbol anchor into this tree. The commercial half — D3, D5.1, D8, D10 and the V1 roadmap — stays in cloud and is still cited as `cloud ADR-0024`; a table in the record says so, because the re-pointing of today's bare citations is per-site and semantic, not a search-and-replace. No decision is added or widened. Files are not moved and numbers are not reassigned, per the maintainer ruling of 2026-09-02. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * docs(adr): renumber the cloud ADR-0024 mirror 0132 → 0135 (0132 is claimed by an open PR) `0132` is free on `main` but claimed on the branch of an open PR — objectstack-ai#16215 adds `docs/adr/0132-multi-organization-runtime-is-open-core.md`. Verified here by a full `git diff --name-only origin/main...<pr-head> -- docs/adr/` over all 27 open PRs, not by titles: 0132 is objectstack-ai#16215's, 0133 is objectstack-ai#16267's, 0134 is objectstack-ai#16476's, and 0135 is unclaimed. File renamed with `git mv`, slug unchanged. The record's one self-citation (its title line) moves with it; the `ADR-0105` and `ADR-0108` references are landed records and are untouched. No line-number anchors were introduced — the file still carries none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com>
…ord's own landing PR and commit Both records landed on `main` on 2026-09-07 (0132 via #16215, squash `c677cda816`; 0133 via #16267, squash `13c08356ab`, both merged by the authorized approver), while their `**Status**` lines still read `Proposed — awaiting the maintainer's hand-merge, which is (itself) the acceptance act`. Read by their own rule the acceptance act occurred; only the line was never updated. Anyone judging citability from the Status field got the wrong answer, and no gate reds on it. Each line now takes the form the positive controls 0130 :3 / 0131 :3 already use — `Accepted (date) — accepted by the merge that landed it on `main` ([#PR](url), commit `sha`), which is (itself) the acceptance act for a governed surface (Prime Directive #14).` — with that record's own landing facts, keeping each file's local form (0132 wraps under a list dash, 0133 is one unwrapped list line). 0132's trailing `Nothing below is settled until this record merges` sentence is dropped, which is what 0130 and 0131 did with theirs; 0133's trailing sentence says something else entirely (this file records no new decision) and is kept byte-for-byte. Nothing else moves: no decision text, no heading, no other line, and ADR-0134 is untouched — its `Proposed` mirrors cloud ADR-0071's own state and is self-explanatory. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
… carries its own landing PR and commit (objectstack-ai#18711) Fixes objectstack-ai#18532 Clause-②: no ## The defect ADR-0132 and ADR-0133 both landed on `main` on 2026-09-07, and both still carried a `**Status**` line reading `Proposed (2026-09-06) — awaiting the maintainer's hand-merge, which is (itself) the acceptance act for a governed surface (Prime Directive objectstack-ai#14)`. Read by the record's own rule, the acceptance act it names had occurred; only the line was never updated — and the Status field is the one machine-readable statement of a governance record's force, so anyone judging "can I cite this as settled law?" from it got the wrong answer while the body below it told them nothing here is settled. The failure direction is silent: both files are on `main`, every gate is green, and nothing reds. The positive controls are in the same convention and one number apart — ADR-0130 and ADR-0131 each moved their line to `Accepted` on landing and pointed back at the PR and commit that landed them. This PR does the same for 0132 and 0133, with each record's own landing facts, and changes nothing else. ## The four Status lines, before and after Measured on this branch's base, `origin/main` at `f8eaf6704` (the card's own readings were taken earlier, on `1e496f979`). The two controls are quoted to show the target form; ⛔ neither is touched by this PR. **ADR-0130 :3 — positive control, unchanged** ```text **Status**: Accepted (2026-09-01) — accepted by the merge that landed it on `main` ([objectstack-ai#14151](objectstack-ai#14151), commit `682d03ba7`), which is itself the acceptance act for a governed surface (Prime Directive objectstack-ai#14). ``` **ADR-0131 :3 — positive control, unchanged** ```text **Status**: Accepted (2026-09-04) — accepted by the merge that landed it on `main` ([objectstack-ai#14976](objectstack-ai#14976), commit `0ed271574`), which is itself the acceptance act for a governed surface (Prime Directive objectstack-ai#14). Execution is paused under objectstack-ai#15193 and the maintainer's standing instruction; that pause is carried there, not by this status line. ``` **ADR-0132 :3 — BEFORE (three physical lines, wrapped under a list dash)** ```text - **Status**: Proposed (2026-09-06) — awaiting the maintainer's hand-merge, which is itself the acceptance act for a governed surface (Prime Directive objectstack-ai#14). ⛔ Nothing below is settled until this record merges. ``` **ADR-0132 :3 — AFTER** ```text - **Status**: Accepted (2026-09-07) — accepted by the merge that landed it on `main` ([objectstack-ai#16215](objectstack-ai#16215), commit `c677cda816`), which is itself the acceptance act for a governed surface (Prime Directive objectstack-ai#14). ``` **ADR-0133 :3 — BEFORE (one unwrapped physical line)** ```text - **Status**: Proposed (2026-09-06) — awaiting the maintainer's hand-merge, which is the acceptance act for a governed surface (Prime Directive objectstack-ai#14). ⛔ Nothing in this file is a new decision; see [Provenance](#provenance--read-this-before-citing-this-file). ``` **ADR-0133 :3 — AFTER** ```text - **Status**: Accepted (2026-09-07) — accepted by the merge that landed it on `main` ([objectstack-ai#16267](objectstack-ai#16267), commit `13c08356ab`), which is the acceptance act for a governed surface (Prime Directive objectstack-ai#14). ⛔ Nothing in this file is a new decision; see [Provenance](#provenance--read-this-before-citing-this-file). ``` Three shape decisions, each read off the controls rather than invented: 1. **Each file keeps its own local form.** 0132 :3 is a wrapped list item under a dash and stays one (three physical lines, wrapped at the same column its sibling bullets use); 0133 :3 is a single unwrapped list line and stays one. ⛔ Neither file is reformatted toward the other, and ⛔ neither is reformatted toward 0130 / 0131, which carry no list dash at all. 2. **0132 keeps `which is itself the acceptance act`; 0133 keeps `which is the acceptance act`.** That word differs between the two files today, and the controls do not settle it — 0130 and 0131 both say `itself`. Each file keeps the wording it already had. 3. **The trailing sentence is treated the way the controls treated theirs.** Neither 0130 :3 nor 0131 :3 carries a "nothing is settled until this merges" clause, so 0132's is dropped rather than turned past tense. 0133's trailing ⛔ sentence is a *different* sentence — it says this file records no new decision and points at Provenance, which is as true after the merge as before — so it is preserved byte-for-byte. ## Landing facts, and how each was verified | record | landing PR | squash commit | merged | merged by | |:--|:--|:--|:--|:--| | ADR-0132 | [objectstack-ai#16215](objectstack-ai#16215) | `c677cda816f70c3e46fa1e3ad451a6d43f50dd07` | 2026-09-07T06:15:26Z (commit date 2026-09-07T05:30:13Z) | os-zhuang | | ADR-0133 | [objectstack-ai#16267](objectstack-ai#16267) | `13c08356abc61f13acd5db0932f99d482c134588` | 2026-09-07T07:37:31Z (commit date 2026-09-07T07:19:25Z) | os-zhuang | Verification, per record, all four legs: - **Single-parent squash.** `git log -1 --format='%P'` returns exactly one parent for each (`acabd24879…` for 0132, `8976ea1120…` for 0133). - **The subject names the PR.** `…(ADR-0132) (objectstack-ai#16215)` and `…mirroring cloud ADR-0081 D1/D3/D4 with Provenance (objectstack-ai#16267)`. - **The commit introduced the file.** `git show --stat SHA -- docs/adr/0132-…md` → `1 file changed, 225 insertions(+)`; the same for 0133 → `273 insertions(+)`. Both are file creations, so each sha is the landing of its own record and not a later edit. - **The sha is an ancestor of `main`** — `git merge-base --is-ancestor SHA origin/main`, exit 0 for both.⚠️ **That ancestry reading needed a control leg, and the first reading was wrong.** This container's checkout is shallow. On the shallow window as fetched (boundary `5de93728a`, 2026-09-09T21:18Z) `--is-ancestor` answered **exit 1 — "not an ancestor"** for both shas, which are dated 2026-09-07 and therefore *outside* the window: a confident false negative, because the missing objects withhold the ancestry path without fabricating one. After `git fetch --shallow-since=2026-08-25 origin main` (694 → 2867 commits) both test legs answer exit 0, and two control commits at least as deep as the tested pair — `682d03ba7` (ADR-0130's own landing, 2026-09-01) and `0ed271574` (ADR-0131's, 2026-09-04) — also answer exit 0, so the negative is retired rather than merely re-rolled. All four exit codes are recorded. The `merged_at` / `merged_by` values come from one REST read each of `/repos/objectstack-ai/objectstack/pulls/16215` and `/pulls/16267`; both report `merged: true`, `base.ref: main`, and a `merge_commit_sha` byte-equal to the sha in the table. This also answers the card's explicitly unmeasured worry about ADR-0133 — whether its landing constituted the hand-merge its own line named. It did: `merged_by` is os-zhuang, the authorized approver. So both records are "the text went stale", ⛔ not "an unaccepted governance record is being cited as law", and one line each is the whole repair. **The date in `Accepted (…)` is the landing date**, as 0130 and 0131 do — 2026-09-07 for both, which is the date on both the squash commit and the merge event. ## Consumers — a reading, no edit `git grep -n 'ADR-0132\|ADR-0133\|0132-multi-organization\|0133-org-management'` at `a937609f6c`: **110 hits across 51 files**. ⛔ None is edited by this PR. By area: | area | files | |:--|--:| | `packages/plugins/**` (organizations, plugin-auth, plugin-dev, plugin-security) | 18 | | `packages/qa/dogfood/**` | 7 | | `packages/cli/**` | 5 | | `packages/verify/**` | 4 | | `packages/spec/**` | 3 | | `docs/adr/**` (0105, 0132, 0133 themselves) | 3 | | `skills/objectstack-data/**` | 2 | | `packages/services/service-cluster/**` | 2 | | `.changeset/**` | 3 | | `content/docs`, `docs/qa`, `packages/types`, `scripts/` | 4 | The card names the census on [objectstack-ai#17379](objectstack-ai#17379) as one reader built on ADR-0132; the grep shows the reliance is much wider than one card — `packages/plugins/organizations` alone cites it in its plugin, its README and nine tests, including pin tests that name the record as the authority for the wall they hold. ## Verification Derived — ⛔ no hand-fed path list — with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree; the tool took the change set itself (three-dot vs the merge base) and printed the two ADR paths. **18 families derived, 18 run, all exit 0**, at head `a937609f6c`: ```text node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-adr-symbol-anchors.mjs :: exit 0 node scripts/check-adr-symbol-anchors.mjs --self-test :: exit 0 node scripts/check-ci-filter-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:adr-anchors :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-prior-rulings :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` Reconciled: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RANFILE` → exit 0, `18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all 18 recorded an exit code and none of them is 3)`. Beyond the derived set: ```text node scripts/pm/check-governed-merges.mjs --test docs/adr/0132-….md docs/adr/0133-….md :: exit 3 pnpm lint (repo-wide, `eslint . --no-inline-config`, ⛔ not narrowed) :: exit 0 ``` **Exit 3 on the governed predicate is the verdict, ⛔ not a failure**: `GOVERNED — a human merge is the review record for this PR (objectstack-ai#9495 regime)`, `docs/adr/** ×2`. This PR is draft and stays draft; ⛔ no seat flips it ready, enqueues it or arms auto-merge. Exit codes were captured with redirect-then-`$?`, ⛔ never across a pipe.⚠️ **One gate refused before it measured anything, and was re-run rather than recorded as a pass.** On the first sweep `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exited **3** — `PREREQUISITE NOT MET — the workspace package @objectstack/formula is not built`, the script's own wording being `Nothing was measured: this gate exited before running a single check`. That is NOT MEASURED, neither green nor red. After `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` (through `scripts/pm/os-verify-lock.sh`, `VERDICT command-exit 0`) it exits 0. Only the exit-0 run is reported above.⚠️ **What this list does not cover** — the derivation says so itself, and it is repeated here so the green is not read wider than it is: 53 artifact-roster families (their `silent` verdict is a fact about a tracked list, ⛔ not a clearance), 11 families whose declared population is too wide to place, 2 families whose argv takes a value only CI supplies, 1 CI job scheduled by these paths whose 5 steps have no local invocation, and 14 families that apply only once a changeset exists — ⛔ not applicable here, see below. Those are CI's to answer on this PR. **Tree state.** `origin/main` moved three commits (`72dd95fa5a`) while this ran, and the derivation flagged the tree stale for one file it reads (`scripts/pm/post-stamped.mjs`). `origin/main` was merged in (5 files, all disjoint from this diff, no `os-regen` deferral), `@objectstack/formula` and `@objectstack/lint` were force-rebuilt because the merge moved `packages/formula/src/validate.ts`, and the whole sweep above was re-derived and re-run at the merged head `a937609f6c`. The re-derivation produced a byte-identical 18-command list and no staleness warning. Every number quoted here is from that head. ## Changeset `skip-changeset`. `docs/adr/**` is on the no-publish fast track: no package's `files[]` ships an ADR, and the diff is four lines of prose in two governance records. `Clause-②: no` — ⛔ nothing an author can write moves. ## Diff ```text docs/adr/0132-multi-organization-runtime-is-open-core.md | 6 +++--- docs/adr/0133-org-management-open-basics.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) ``` Two files, one logical Status line each. 0132 counts as three physical lines because its line is a wrapped bullet, before and after. ## Acceptance notes ⛔ None of the following is filed as a card; each is recorded here for the reviewing seat. 1. **ADR-0133 :85–:97 carries a pre-merge frame that survived its own merge, and is deliberately ⛔ not touched.** The block is headed `⚠️ **A note for the hand-merge, stated rather than decided.**` and ends `**If the maintainer prefers one combined record, that is still a merge-time call** — the two files are disjoint, so folding them is a move, not a rewrite.` It addresses a reader who has not merged the file yet. Quoted here because the dispatch required any such sentence be quoted before touching it: ⛔ it is not touched, because it asserts nothing about this record's acceptance state — it offers a structural option about folding 0133 into 0132 — and scope excludes every other line in both files. The same block already reads `That obstacle is gone — the record has landed`, about ADR-0132. 2. **Two premises in the dispatch text did not survive measurement on this tree; neither changed the deliverable.** (a) The dispatch said the `acceptance act` phrase occurs in the *bodies* of 0132 (twice) and 0133 (once), to be left untouched. Measured: `grep -c` gives **1** in each file, and in both cases that single occurrence **is** the Status line this PR edits — there is no body occurrence in either file to leave alone. The 2× / 1× counts belong to 0130 / 0131, which is what the card itself says. (b) The dispatch described 0133's Status line as carrying the `Nothing … is settled until this record merges` sentence. It does not; its trailing ⛔ sentence is the unrelated `Nothing in this file is a new decision` one, which is preserved. The card's own table quoted 0133 correctly. 3. **Short-sha spelling.** 0130 and 0131 use 9 hex digits, which is what `git rev-parse --short` emitted at their time. On this tree, after deepening, the default abbreviation is 10 — and 10 is also the spelling the card body and the claim comment both recorded (`c677cda816`, `13c08356ab`). This PR uses 10 so that the file, the card and the claim agree; the full 40-hex shas are in the landing table above. The sentence *form* is the controls' form exactly. 4. **⛔ ADR-0134 and the other 17 `Proposed` records are untouched**, per the card. 0134's `Proposed` mirrors cloud ADR-0071's own state and is self-explanatory; the other 17 were never measured by the card and a batch edit would treat an unmeasured population as a measured one. ## 维护者速读(草稿) **改了什么** —— 两份治理记录(ADR-0132、ADR-0133)的 `**Status**` 行,各改一行:从「Proposed —— 等待维护者手工合并,而那次合并本身就是接受行为」改成「Accepted (2026-09-07) —— 由把它落到 `main` 上的那次合并接受」,并各自回指自己的落地 PR 与提交(0132 → objectstack-ai#16215 / `c677cda816`;0133 → objectstack-ai#16267 / `13c08356ab`)。两份文件的其余内容、另外 17 份 `Proposed` 的 ADR、以及看着同形但必须排除的 0134,全部未动。整个 diff 是 2 个文件、4 行增、4 行删。 **为什么改** —— 这两份 ADR 早在 2026-09-07 就已经合并进 `main`,而且都是由授权批准人 os-zhuang 合的 —— 也就是说,它们自己那行字所说的「接受行为」**确实已经发生了**,只是没有人回去改那一行。ADR 的 Status 字段是这份记录唯一一处机器可读的效力声明:今天任何人(或任何工具)按它判断「这条能不能当既定法引用」,得到的都是错的答案,而正文还在要求读者「在这份记录合并之前,下面的一切都未定」。实测有 **51 个文件、110 处**引用这两份 ADR,其中 `packages/plugins/organizations` 的插件、README 和九个测试都把它当权威引用。⚠️ 这个失效是**沉默的**:两份都在 `main` 上,CI 全绿,没有任何门禁会因此变红 —— 所以它只能靠人读出来。同一套约定的 ADR-0130 / 0131 落地后都改成了 `Accepted` 并回指落地 PR,本 PR 照抄它们的拼法,⛔ 没有发明第三种。 **风险与代价(含回滚)** —— 风险极低:改的是四行散文,不改任何决策文本、任何标题、任何代码,不发布任何 npm 包内容(故 `skip-changeset`)。本地推导出的 18 个门禁族全部 exit 0,仓库级 `pnpm lint` 也 exit 0。⚠️ 唯一需要您知道的判断:**这两份记录被判定为「文字过时」,而不是「未被接受却已被当法引用」** —— 依据是两份的合并人都是 os-zhuang(授权批准人),卡片里原本存疑的正是 ADR-0133 这一条,现已查实。若您认为 ADR-0133 那次合并**不**构成它所说的接受行为,那么正确的修法与本 PR **相反**(要补一次接受行为,而不是改一行字),此时请直接关掉本 PR —— 回滚成本就是关掉它,没有任何其他产物需要撤。 **席位意见** —— **你要做的** —— ① 确认上面那条判断:ADR-0133 由 os-zhuang 合并,是否算作它那行字所说的「维护者手工合并 = 接受行为」。② 若认可,本 PR 是受管面(`docs/adr/**`),按 Prime Directive objectstack-ai#14 需要您本人的手工合并或一次授权批准 —— ⛔ 本 PR 恒为 draft,任何 AI 席位都不会把它翻成 ready、不会入队、不会开 auto-merge。③ 若您希望连带把另外 17 份 `Proposed` 的 ADR 一起盘一遍,那是另一张卡 —— 本卡刻意没量它们。 --- _Generated by [Claude Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ Co-authored-by: Claude <noreply@anthropic.com>
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.⭐ Update, 2026-09-07 — the dependency landed, so this file's account of it had gone stale
ADR-0132 is on
main. The record that reverses cloud ADR-0081 D2 — The multi-organization runtime is open core — landed asdocs/adr/0132-multi-organization-runtime-is-open-core.md(squashc677cda81, 2026-09-07 05:30 UTC), together with the now-openpackages/plugins/organizations. Measured, not assumed: onorigin/main,git log --oneline | grep -cfor that PR's squash suffix returns 1 (control: a known-landed sibling PR also returns 1), the ADR file resolves undergit cat-file -e origin/main:docs/adr/0132-..., and so does the package manifest.Six places in this file were written against the opposite fact. Each is anchored by its text, not by a line number:
- **Deliberately not mirrored**:## Relationship to the D2 reversal (…)main"(ADR-0132); body cites[ADR-0132](./0132-multi-organization-runtime-is-open-core.md)and states plainly that it landedmain"⚠️ A note for the hand-mergemain"D1s in one document, which is precisely what the deliberately unrenumbered mirror exists to keep checkable. Stated, not decided: this change takes the option neither way, and the disposition stays a merge-time callWhat this record does not decide, item 4[ADR-0132](./0132-multi-organization-runtime-is-open-core.md)The file wrote its own trigger for this edit, and the condition is now met. Zero occurrences of the old PR number survive anywhere in the file (
grep -creturns 0).⛔ Nothing about the reversal is re-derived, restated or evaluated here. ADR-0132 records, in its own "What this record does not decide" section, that it does not touch cloud ADR-0081 D1, D3 or D4 — which are this file's entire content. The two records stay disjoint; this one only cites.
origin/mainwas merged into the branch (merge commitc83097a1f) so the newly-cited record exists in the tree the gates read. The PR's own diff is still exactly one file.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. 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
ls docs/adr/onorigin/mainat77781151d, when the file was written — the highest record was 0131.⇒ 0133, and the number below it is no longer a gap: 0132 is a real, merged record.
⭐ Two findings that shaped how the file is written
1. A line-number anchor in an ADR is a hard gate failure. The card supplied three
file.tsplus line-offset positions.check:adr-symbol-anchors(the #13556 migration, maintainer ruling 2026-09-01) makes any surviving line anchor underdocs/adr/**a finding with no transition period — the census behind it measured 243 of 337 line anchors already broken. Every anchor in this file is therefore a symbol anchor (path#symbol) or a file-level path, and the gate re-confirms it: 0 line anchors survive.2. The record being cited could not be named by number — and now can. The file originally cited ADR-0132 by number and
check:adr-anchorsfailed: a citation of an ADR number naming no record underdocs/adr/is a squat, retroactively falsified if the record lands under a different number. The reversal was therefore referenced by PR number, with the reason recorded in the file so the next author would not undo it. That constraint is discharged by this round — the record exists, the number resolves, and the gate stays green with the citation in place.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/mainat77781151dand re-checked on today'sorigin/main: 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.
Observed while re-checking, deliberately not touched
docs/adr/0132-multi-organization-runtime-is-open-core.mdstill carries- **Status**: Proposed (2026-09-06) — awaiting the maintainer's hand-mergealthough it has merged. That is residue on another record, owned by whoever lands its correction; ⛔ nothing in this PR edits it.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. 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 was applied additively and read back on an earlier round; it is present on this PR now.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 commit01b450d40) and takes its change set from the merge base rather than a hand-built diff.17 commands derived, 17 green at
01b450d40— the union was re-derived and re-run in full after the final commit, and the derivation confirmed the same 17-command family. Each exit code was captured immediately after a single redirected command, never through a pipe. The verdict lines are the gates' own:check-adr-symbol-anchors— 1970 anchors across 137 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; 131 decision number(s), each naming one decision or an allowlisted pair; 33638 citation(s) across 4260 file(s) resolve; 997 decision-letter citation(s) on the anchored surface name a decision the record makes).check-adr-links— 667 relative link destination(s) under docs/adr/ resolve (this is what re-checks the new relative links and the heading anchor the header bullet points at)check-doc-authoring— 396 files clean — no bare metadata literalscheck-nul-bytes— OK (scanned 8128 text file(s) … no raw ASCII control bytes)check-closing-keyword-parity— OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8135 tracked file(s), all registered).check-ci-filter-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-testhalves — all exit 0.One gate first answered exit 3 — PREREQUISITE NOT MET, which is neither a pass nor 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 (scripts/pm/os-verify-lock.sh— VERDICT command-exit 0 · held the lock 188s · waited 0s), then re-run to a real reading: 22 record-scoped formula example(s) across 432 files / 1371 TS blocks judged clean, plus its spec-TSDoc and field-level legs.Governance predicate, asserted rather than assumed:
node scripts/pm/check-governed-merges.mjs --test docs/adr/0133-org-management-open-basics.md→ exit 3, GOVERNED, "a human merge is the review record for this PR". That is the intended reading, not a failure.The repo-wide
pnpm lintsweep is CI's; the local scope was narrowed and the narrowing is measured, not assumed. Three readings: the population comes from eslint's own flat config, not from a guess — run against the changed path it answers "File ignored because no matching configuration was supplied";--format jsonreports 0 linted files for that path (one entry,errorCount: 0, carrying only the ignore notice); and no eslint configuration file is in this diff, so no untouched file's verdict can move either. No CI state is predicted here.Generated by Claude Code