docs(identity): re-point the SCIM/identity ADR-0071 citations at the records that mean them - #18098
Conversation
…records that mean them
`ADR-0071` names two unrelated decisions from this repo's point of view. The
record under `docs/adr/0071-*` is *Dataset semantic-layer depth — multi-hop
joins*; the identity/SCIM citations mean the enterprise-identity decision taken
in `objectstack-ai/cloud`, whose open mechanism half is now mirrored here as
ADR-0134 (landed 2026-09-07). Every identity citation therefore resolved to a
real page about the wrong subject.
Re-points 44 bare identity-meaning citations, per director ruling B as amended:
- 43 -> `ADR-0134` — the open mechanism half (SCIM forces the admin plugin on,
`active:false` -> ban, the env-side Service Provider, the seven stable SCIM
models). ADR-0134 is a local record with anchors into exactly these files.
- 1 -> `cloud ADR-0071` — `auth-manager.ts`'s "the paid Identity lifecycle",
which names the commercial half that stays in the cloud record.
Untouched, deliberately: the 22 dataset-meaning citations (they match the local
record), the 6 CHANGELOGs (historical archive), `docs/adr/**` (governed), and
`auth-plugin.ts`'s already-qualified `cloud ADR-0071 verification #1`.
Bare `ADR-0071` still resolves exactly as before — the qualifier only adds
precision, it does not weaken the gate.
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Co-authored-by: Claude <noreply@anthropic.com>
…ribe strings `content/docs/references/system/auth-config.mdx` is generated from `packages/spec/src/system/auth-config.zod.ts`'s `.describe()` text; the two admin-flag rows follow the producer to ADR-0134. Regenerated with `pnpm --filter @objectstack/spec gen:schema && gen:docs`, never hand-edited. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
Measured rather than assumed: all three packages ship the changed bytes. `@objectstack/spec` lists `src/**/*.zod.ts` in `files[]` so the describe string is published verbatim, and the generated `json-schema/` bundle carries it too; `plugin-auth`'s `dist/index.mjs` carries the operator-facing refusal message; `platform-objects`' `dist/index.mjs` carries the nine `protection.reason` strings. Patch, not minor: no export, schema or accept/refuse face moves. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 140 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8528b815cd97dea8271f91098a5df141eb5f3737 && git checkout 8528b815cd97dea8271f91098a5df141eb5f3737
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a26a114d7e0f29c2459b225f5a4df2846417b49d 8d2dfb3ac1f2432502b5a192a74b5b857b926a9d && git checkout -B drift-repro a26a114d7e0f29c2459b225f5a4df2846417b49d && git merge --no-ff 8d2dfb3ac1f2432502b5a192a74b5b857b926a9d
node scripts/docs-audit/affected-docs.mjs --json a26a114d7e0f29c2459b225f5a4df2846417b49d
|
Part of #14361 — ⛔ this PR deliberately does NOT close it; see "What this PR leaves open on the same card" below.
Clause-②: no
Director ruling B — the repo-qualified spelling (comment
5507409725), as amended by the same director seat in comment5507573601, which is the operative form of the ruling and is independently mandated by AGENTS.md Prime Directive #13. See "The one place this PR departs from the dispatch brief" below — please read it before reviewing the diff.What was wrong
From this repository's point of view
ADR-0071named two unrelated decisions and only one of them had a record here.docs/adr/0071-dataset-semantic-layer-depth.mdis ADR-0071: Dataset semantic-layer depth — multi-hop joins.objectstack-ai/cloud, whose open mechanism half has been mirrored into this repo since 2026-09-07 as ADR-0134.check:adr-anchorswas green over all of them, because the number resolves — it just resolves to a real page about the wrong subject, which is worse than a dangling id: a plausible record invites belief instead of a second question.The count, re-derived from the tree
The card said 39, measured on
e854a531a. Re-derived on this branch's merge base66e34d14d, per line rather than per file:ADR-0134, 1 →cloud ADR-0071)auth-plugin.ts:1210cloud ADR-0071 verification #1docs/adr/**(the 0071 record, ADR-0134, ADR-0135)content/docs/references/system/auth-config.mdxADR-0071lines in tracked files (pnpm-lock.yamlexcluded)How 44 differs from 39. The card counted files (43, of which 39 identity); this counts citation lines, and several files carry more than one (
auth-manager.ts8,auth-manager.test.ts9,auth-config.zod.ts4). The surface also grew betweene854a531aand today: the two mirror ADRs landed,auth-manager.test.tsandlast-admin-guard.{ts,test.ts}gained citations, and the two changesets the card counted have since been released into CHANGELOGs (.changeset/carries noADR-0071today). 24 source files are touched; the 39 in the card is neither the file count nor the line count of what actually needed moving.How each citation was classified — per site, by meaning, never by path
The split is not a judgement call this PR invented. AGENTS.md Prime Directive #13 states it directly:
So the question asked at every site was: which half of the cloud decision is this sentence about?
(b) the open mechanism half →
ADR-0134, 43 sites. Every one of them is describing code that lives in this repository, and ADR-0134's own Consumers list names those exact files:plugin-auth(14) — effective SCIM forces the better-authadminplugin on; the construction-time refusal whenplugins.admin: falsesits beside effective SCIM;active:false→ ban + session revocation;@better-auth/scimaccepting noschemaoption; the SCIM/SSO adapter model map.plugin-authtests (10) — the four pins on the refusal message, re-judged in place (see below).spec(7) — theadminflag's docblock and.describe()text,public-auth-features.ts'snotes, and the v17default-changes.tsupgrade note.platform-objects(9) — theprotection.reasonon the eightsys_scim_*objects and thesys_useraction note.qa/dogfood(3),pnpm-workspace.yaml(1, the rc.2 seven-model migration),docs/qa/platform-checklist/areas/identity-auth.json(1).(a) the commercial / boundary half →
cloud ADR-0071, 1 site.auth-manager.ts:3682, "the paid Identity lifecycle" — that is D6 of the cloud record, the paid-Enterprise-Identity framing which ADR-0134 §What stays in the cloud record explicitly refuses to restate. It stays cited to cloud.Dataset meaning → untouched, 22 sites. Each was read, not inferred: all 22 are about multi-hop
includechains, the 3-hop limit, join allowlists and Cube joins —service-analytics(15),spec/src/ui/dataset.zod.ts(3),lint/validate-dataset-references.{ts,test.ts}(2),analytics.mdx(1),query-syntax.mdx(1).Positive control against over-rewriting
The discrimination rule is not a regex over the id — it is an explicit per-file allowlist of identity files, with a qualifier-aware substitution inside them. Two independent proofs that nothing on the dataset side moved:
and, in the other direction, the already-qualified
cloud ADR-0071atauth-plugin.ts:1210survived a pass over its own file untouched — the substitution skips any id already carrying aCROSS_REPO_QUALIFIERSword.Bare
ADR-0071still resolves — the gate is not weakenedIdentical summary lines, before and after:
22 bare
ADR-0071citations remain in the tree and the gate resolves every one of them. Ablation, to show that is a measurement and not a vacuous pass — one bare dataset citation mutated to an id with no record:The restore was settled by blob hash plus an empty
git diff HEAD, ⛔ never by an exit code —trapis unreliable in this container (#17875).Pins re-judged in place, ⛔ none deleted
auth-manager.test.tscarries fourtoThrow(/…ADR-0071…/)pins on the operator-facing refusal message and one test name. They pin the message's content, and the content moved, so the pins move with it — the assertions still pin exactly what they pinned before: that the refusal names the ADR that explains the coupling. Reverse-verified that they are live rather than decorative: with the shipped message mutated back toADR-0071, the two message pins fail loudly.Generated docs
content/docs/references/system/auth-config.mdxlines 105 and 215 follow their producer (auth-config.zod.ts's.describe()), regenerated withpnpm --filter @objectstack/spec gen:schema && … gen:docs. Exactly two lines drifted; nothing else in the 222 generated files moved.check:docs,check:generatedandcheck:authorable-surfaceare green.Changeset — measured, not defaulted
patchfor@objectstack/plugin-auth,@objectstack/platform-objects,@objectstack/spec. Published bytes really do move, measured against each package'sfiles[]after a build:@objectstack/spec—files[]listssrc/**/*.zod.ts, so the changed.describe()ships verbatim; the generatedjson-schema/bundle (also infiles[]) carries it too.@objectstack/plugin-auth—dist/index.mjscarries 4ADR-0134, including the operator-facing refusal string (positive control: a shipped literal greps at 1; negative control: a comment-only marker greps at 0).@objectstack/platform-objects—dist/index.mjscarries the 9protection.reasonstrings.patchand notminor: no export, no schema shape, and no accept/refuse face moves. The refusal fires on exactly the condition it fired on before; only the ADR number inside its sentence changes. The changeset says so, because a deployment grepping that message forADR-0071is the one consumer this can surprise.Reverse-read — which existing sentence does this make false?
Three, all of them in
docs/adr/**, which this lane ⛔ must not touch (governed surface, PD #14). None is falsified in substance; each goes tense-stale:docs/adr/0134-env-side-scim-provisioning.md:36— "Identity code that writes a bareADR-0071today therefore cites, by this repo's own convention, the wrong document." After this lands, no identity code does. The sentence's headline claim — thatADR-0071is an ambiguous string in this repo — stays true: the local 0071 is still the dataset record and cloud's 0071 still exists.docs/adr/0134:38and:294— "Re-pointing the existing bare citations is [finding] The SCIM/identity ADR-0071 citation resolves to the dataset semantic-layer record — 39 files point at a decision about multi-hop joins #14361's work and is deliberately ⛔ not done by this file." Still true about the file; the pointer becomes past tense.docs/adr/0135-identity-and-access-architecture.md:59— "Re-pointing the identity surface's existing bare citations at this record is [[finding] The SCIM/identity ADR-0071 citation resolves to the dataset semantic-layer record — 39 files point at a decision about multi-hop joins #14361]" — same class, and see the scope note below, because theADR-0024half of that sentence is still outstanding and still true.Reverse direction — a sentence this makes true rather than false:
docs/adr/0134:37, "Always writecloud ADR-0071for the SCIM record, andADR-0134for this one." That instruction was correct and simply unobeyed by the tree; this PR is the tree obeying it. Zero other sentences in the tree assert a present-tense count or claim about these citations — the only39 filesstrings in the repo belong topackages/cliand are about something else entirely.The one place this PR departs from the dispatch brief
The dispatch brief asks for every identity citation to read
cloud ADR-0071, citing ruling5507409725. Three sources onmainsay otherwise, and they agree with each other:5507573601, same director seat, 14 minutes later, self-titled "Ruling amended": "Target for the (b) set (citations that mean the cloud decision's open mechanism half): the new local ADR numbers that ADR (mirror of cloud ADR-0024): Identity & Access Architecture — the open half thatplugin-auth/platform-objects/identityimplement, recorded in this repo with Provenance #14506 / ADR (mirror of cloud ADR-0071): env-side SCIM via@better-auth/scim— the open mechanism half (D1–D4), recorded in this repo with Provenance and the measured 1.7.x vendor drift #14507 / ADR (mirror of cloud ADR-0081): Organization management — the open basics half (D1 invitations stay open, D3 org record page, D4 org-scopedsys_member), recorded in this repo with Provenance #14508 land — notcloud ADR-NNNN", and "the operator-facing refusal text inauth-manager.ts:359re-pointed to the new local number".5594577301that unblocked this card restates it: "the (b) citation targets are the new local ADR numbers those cards land", naming ADR-0134 as the mirror of cloud 0071.Under the unamended reading, ruling B is still satisfied by this diff (every identity citation is now unambiguous and the gate discriminates), but 43 of the 44 would read
cloud ADR-0071instead ofADR-0134. If the reviewer prefers that reading it is a one-command flip on this branch; I did not pick a side silently, which is why this section exists.What this PR leaves open on the same card
The amendment also folds the sibling collision class into this card — bare
ADR-0024and bareADR-0081, the mirrors of which landed as ADR-0135 and ADR-0133 — "one card over the identity surface (0071 + 0024 + 0081) … so it is not paid twice". The dispatch brief scopes this lane toADR-0071only, and the sibling surface is large: 151 bareADR-0024lines and 86 bareADR-0081lines, most of which legitimately mean this repo's own0024-mcp-connectorsand0081-trusted-react-page-tierrecords and must not move.docs/adr/0135:260says so in as many words — "Whether any individual bareADR-0024citation should move … cannot be a search-and-replace". That is a real per-site pass with its own budget, anddocs/adr/0135:260says so in as many words.objectql-adapter.ts:58now readsSee ADR-0024 / ADR-0134., where theADR-0024half is knowingly left for it.⭐ This PR therefore delivers the
ADR-0071third of #14361 — the 44 sites above — and nothing else. The per-site judgement over bareADR-0024and bareADR-0081is the remaining work of the same card, not a new one: the amendment's Scope line reads "the identity surface's whole collision class, one pass: bareADR-0071, bareADR-0024, bareADR-0081— every citation read for its meaning", anddocs/adr/0135:260calls theADR-0024half "#14361's per-site call". That is why the first line of this body saysPart of #14361and notCloses: merging this while two thirds of the ruled scope is unwritten would close the card on a third of its work. The card stays open for the next round.Verification
pnpm check:adr-anchorsmainpnpm --filter @objectstack/{spec,plugin-auth,platform-objects} testtypecheckcheck:test-typecheckledgers, unmoved)check:authorable-surface·check:docs·check:generatedcheck:platform-checklist·check:doc-anchors·check:docs-single-h1·check:doc-authoring·check:docs-spec-enumerationscheck:nul-bytes·check:published-files·check:corpus-claim-drift·check:quick-reference-counts·check:comment-mask-{adoption,corpus}·check:spec-docblock-symbol-anchorscheck-changeset-fixed,check-changeset-no-major,check-empty-changeset,check:changeset-gate-self-tests)check:doc-frontmatter·check:docs-section-name·check:keyed-text-bounds·check:platform-object-tenancy-census·check:reference-carrier-shape·check:adr-0087-registrationeslint . --no-inline-config— the whole repo, not a narrowing8d2dfb339 gate commands, every one
exit 0, captured before any pipe.Declared narrowing.
node scripts/pm/dispatch-gates.mjs --randerives 123 commands from this change set and accounts 39 of them. I ran the families this card names plus every one I could see implicated, and — since it turned out to fit the foreground budget — the repo-wide lint union rather than a narrowing of it. The remaining derived families are CI's farm, not this lane's run.packages/qa/dogfood's three edits are comment-only — no dogfood boot was run locally, and that layer is declared to CI.Generated by Claude Code