Skip to content

docs(identity): re-point the SCIM/identity ADR-0071 citations at the records that mean them - #18098

Merged
claude[bot] merged 3 commits into
mainfrom
claude/issue-14361-adr-0071-citations
Sep 14, 2026
Merged

docs(identity): re-point the SCIM/identity ADR-0071 citations at the records that mean them#18098
claude[bot] merged 3 commits into
mainfrom
claude/issue-14361-adr-0071-citations

Conversation

@claude

@claude claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

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 comment 5507573601, 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-0071 named two unrelated decisions and only one of them had a record here.

  • docs/adr/0071-dataset-semantic-layer-depth.md is ADR-0071: Dataset semantic-layer depth — multi-hop joins.
  • The identity / SCIM citations mean something else entirely: the enterprise-identity decision taken in objectstack-ai/cloud, whose open mechanism half has been mirrored into this repo since 2026-09-07 as ADR-0134.

check:adr-anchors was 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 base 66e34d14d, per line rather than per file:

bucket lines disposition
identity meaning, bare 44 re-pointed (43 → ADR-0134, 1 → cloud ADR-0071)
identity meaning, already repo-qualified 1 untouched — auth-plugin.ts:1210 cloud ADR-0071 verification #1
dataset meaning 22 ⛔ untouched — they match the local record
docs/adr/** (the 0071 record, ADR-0134, ADR-0135) 18 ⛔ untouched — governed surface
six package CHANGELOGs 15 ⛔ untouched — historical archive
generated content/docs/references/system/auth-config.mdx 2 regenerated from its producer, never hand-edited
total ADR-0071 lines in tracked files (pnpm-lock.yaml excluded) 102 over 46 files

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.ts 8, auth-manager.test.ts 9, auth-config.zod.ts 4). The surface also grew between e854a531a and today: the two mirror ADRs landed, auth-manager.test.ts and last-admin-guard.{ts,test.ts} gained citations, and the two changesets the card counted have since been released into CHANGELOGs (.changeset/ carries no ADR-0071 today). 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:

Decisions that draw the open/closed or commercial boundary live in objectstack-ai/cloud and are cited from this repo as cloud ADR-NNNN — ⛔ never as a bare number […] When a cloud decision's mechanism half governs open code here, this repo carries its own ADR — own number […] the commercial half left in cloud.

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-auth admin plugin on; the construction-time refusal when plugins.admin: false sits beside effective SCIM; active:false → ban + session revocation; @better-auth/scim accepting no schema option; the SCIM/SSO adapter model map.
  • plugin-auth tests (10) — the four pins on the refusal message, re-judged in place (see below).
  • spec (7) — the admin flag's docblock and .describe() text, public-auth-features.ts's notes, and the v17 default-changes.ts upgrade note.
  • platform-objects (9) — the protection.reason on the eight sys_scim_* objects and the sys_user action 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 include chains, 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:

$ git diff --stat origin/main -- packages/services/service-analytics packages/lint \
      packages/spec/src/ui/dataset.zod.ts content/docs/data-modeling/analytics.mdx \
      content/docs/protocol/objectql/query-syntax.mdx docs/adr/
(empty)

and, in the other direction, the already-qualified cloud ADR-0071 at auth-plugin.ts:1210 survived a pass over its own file untouched — the substitution skips any id already carrying a CROSS_REPO_QUALIFIERS word.

Bare ADR-0071 still resolves — the gate is not weakened

Identical summary lines, before and after:

main   check-adr-anchors: OK (53 anchored file(s) … 35477 citation(s) across 4521 file(s) resolve; 1022 decision-letter citation(s) …)
HEAD   check-adr-anchors: OK (53 anchored file(s) … 35477 citation(s) across 4521 file(s) resolve; 1022 decision-letter citation(s) …)

22 bare ADR-0071 citations 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:

on-disk proof  : ADR-0071 3 -> 2 ; ADR-9071 injected = 1
ABLATION check-adr-anchors exit = 1
  • ADR-9071 is cited by 1 file(s) but names no record under docs/adr/ —
restored blob  : 5c90e77cd7506130d2614357cab1a19d990e31de
RESTORE SETTLED: blob matches HEAD and 'git diff HEAD' is empty

The restore was settled by blob hash plus an empty git diff HEAD, ⛔ never by an exit code — trap is unreliable in this container (#17875).

Pins re-judged in place, ⛔ none deleted

auth-manager.test.ts carries four toThrow(/…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 to ADR-0071, the two message pins fail loudly.

MUTATED vitest exit = 1
AssertionError: expected [Function] to throw error matching /plugins\.admin[\s\S]*ADR-0134[\s\S]*p…/
AssertionError: expected [Function] to throw error matching /OS_SCIM_ENABLED[\s\S]*ADR-0134/
restored : f9b5c2a60a51e6d2174c91d9602cb7b59d3a6d94
RESTORE SETTLED: blob == HEAD and 'git diff HEAD' empty

Generated docs

content/docs/references/system/auth-config.mdx lines 105 and 215 follow their producer (auth-config.zod.ts's .describe()), regenerated with pnpm --filter @objectstack/spec gen:schema && … gen:docs. Exactly two lines drifted; nothing else in the 222 generated files moved. check:docs, check:generated and check:authorable-surface are green.

Changeset — measured, not defaulted

patch for @objectstack/plugin-auth, @objectstack/platform-objects, @objectstack/spec. Published bytes really do move, measured against each package's files[] after a build:

  • @objectstack/specfiles[] lists src/**/*.zod.ts, so the changed .describe() ships verbatim; the generated json-schema/ bundle (also in files[]) carries it too.
  • @objectstack/plugin-authdist/index.mjs carries 4 ADR-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-objectsdist/index.mjs carries the 9 protection.reason strings.

patch and not minor: 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 for ADR-0071 is 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:

  1. docs/adr/0134-env-side-scim-provisioning.md:36"Identity code that writes a bare ADR-0071 today therefore cites, by this repo's own convention, the wrong document." After this lands, no identity code does. The sentence's headline claim — that ADR-0071 is an ambiguous string in this repo — stays true: the local 0071 is still the dataset record and cloud's 0071 still exists.
  2. docs/adr/0134:38 and :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.
  3. 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 the ADR-0024 half of that sentence is still outstanding and still true.

Reverse direction — a sentence this makes true rather than false: docs/adr/0134:37, "Always write cloud ADR-0071 for the SCIM record, and ADR-0134 for 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 only 39 files strings in the repo belong to packages/cli and 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 ruling 5507409725. Three sources on main say otherwise, and they agree with each other:

  1. The ruling's own amendment, comment 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 that plugin-auth / platform-objects/identity implement, 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-scoped sys_member), recorded in this repo with Provenance #14508 land — not cloud ADR-NNNN", and "the operator-facing refusal text in auth-manager.ts:359 re-pointed to the new local number".
  2. The release comment 5594577301 that 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.
  3. AGENTS.md Prime Directive [WIP] Add Chinese version of the documentation #13, quoted above, which is binding regardless of any comment and says mechanism-half → this repo's own number.

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-0071 instead of ADR-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-0024 and bare ADR-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 to ADR-0071 only, and the sibling surface is large: 151 bare ADR-0024 lines and 86 bare ADR-0081 lines, most of which legitimately mean this repo's own 0024-mcp-connectors and 0081-trusted-react-page-tier records and must not move. docs/adr/0135:260 says so in as many words — "Whether any individual bare ADR-0024 citation should move … cannot be a search-and-replace". That is a real per-site pass with its own budget, and docs/adr/0135:260 says so in as many words. objectql-adapter.ts:58 now reads See ADR-0024 / ADR-0134., where the ADR-0024 half is knowingly left for it.

This PR therefore delivers the ADR-0071 third of #14361 — the 44 sites above — and nothing else. The per-site judgement over bare ADR-0024 and bare ADR-0081 is 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: bare ADR-0071, bare ADR-0024, bare ADR-0081 — every citation read for its meaning", and docs/adr/0135:260 calls the ADR-0024 half "#14361's per-site call". That is why the first line of this body says Part of #14361 and not Closes: 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

what result
pnpm check:adr-anchors green, byte-identical summary to main
pnpm --filter @objectstack/{spec,plugin-auth,platform-objects} test 476 + 111 + 40 files, 16 517 tests, all pass
same three, typecheck green (incl. check:test-typecheck ledgers, unmoved)
check:authorable-surface · check:docs · check:generated green
check:platform-checklist · check:doc-anchors · check:docs-single-h1 · check:doc-authoring · check:docs-spec-enumerations green
check:nul-bytes · check:published-files · check:corpus-claim-drift · check:quick-reference-counts · check:comment-mask-{adoption,corpus} · check:spec-docblock-symbol-anchors green
changeset gates (check-changeset-fixed, check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests) green
check:doc-frontmatter · check:docs-section-name · check:keyed-text-bounds · check:platform-object-tenancy-census · check:reference-carrier-shape · check:adr-0087-registration green
eslint . --no-inline-config — the whole repo, not a narrowing 6743 files, 0 errors, 0 warnings, at 8d2dfb3

39 gate commands, every one exit 0, captured before any pipe.

Declared narrowing. node scripts/pm/dispatch-gates.mjs --ran derives 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

…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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:system tests tooling labels Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/spec, touching 7 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/auth-schema-config.ts, packages/plugins/plugin-auth/src/last-admin-guard.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/auth-service.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AUTH_MODEL_TO_PROTOCOL (symbol, a top-level const object), AuthManager (symbol, a top-level class))
  • content/docs/permissions/sso.mdx (via getPublicConfig (symbol, a method of class AuthManager))
What this run could not see
  • 2 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/auth-schema-config.ts, packages/plugins/plugin-auth/src/last-admin-guard.ts) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 140 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a26a114d7e0f29c2459b225f5a4df2846417b49dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 8528b815cd97dea8271f91098a5df141eb5f3737 — the merge of head 8d2dfb3ac1f2432502b5a192a74b5b857b926a9d into base a26a114d7e0f29c2459b225f5a4df2846417b49d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a26a114d7e0f29c2459b225f5a4df2846417b49d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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 protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant