Skip to content

fix(plugin-auth): open the SCIM request scope at handleRequest so SCIM provisioning runs inside one engine transaction (#14522) - #14624

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-14522-scim-transaction-scope
Sep 2, 2026
Merged

fix(plugin-auth): open the SCIM request scope at handleRequest so SCIM provisioning runs inside one engine transaction (#14522)#14624
os-sales merged 2 commits into
mainfrom
claude/issue-14522-scim-transaction-scope

Conversation

@os-sales

@os-sales os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14522

What changed

@better-auth/scim wraps every User/Group mutation in runIdentityMutationTransaction@better-auth/core's runWithTransaction(adapter, fn)adapter.transaction(...). On this adapter, transaction opens a real engine.transaction() only while inScimRequestScope() reads true. That scope was stamped with AsyncLocalStorage.enterWith inside the verifyBearerToken callback handed to the SCIM plugin — and at write time the store was gone, so the vendor's transaction callback ran with no engine transaction at all (the #3653 scoping note in objectql-adapter.ts declared the opposite).

The fix opens the scope where it is observed: AuthManager.handleRequest now wraps the whole request in scimRequestScope.run({ scim: true }, ...) when the better-auth endpoint path is under /scim/v2 — the same door the actor-attribution scope (runWithAuthActorScope) and the subject-erasure transaction (runSubjectErasureAtomically) already use, keyed on the same betterAuthEndpointPath(request) reading. run has a callback boundary; every als.run the vendor performs underneath nests inside it. The verifier no longer stamps anything. scimRequestScope / inScimRequestScope stay in use, unchanged in shape.

Files: auth-manager.ts (the door scope + SCIM_PROTOCOL_PATH_PREFIX, the verifier's stamp removed, the reconcileScimUserLifecycle doc corrected), objectql-adapter.ts (the #3653 note rewritten — where the scope is stamped now and why the verifier stamp never propagated), scim-connection-service.ts (the ALS doc rewritten), user-ban-write.ts (one stale "once that transaction is real" clause corrected — prose only, the write is untouched), scim-deactivation-reconcile-user.test.ts (face (c) line 487 flipped falsetrue deliberately, comment rewritten; line 501 untouched), new scim-transaction-scope.test.ts (the runtime pin), one patch changeset for @objectstack/plugin-auth.

Triage scope guards (14522#issuecomment-5507946166, quoted verbatim)

  1. Do not make non-SCIM better-auth flows transactional. objectql-adapter.ts:826-828 records that the conditional is load-bearing against "two measured breakages", and the historical sequential behaviour for non-SCIM flows is deliberate. Whatever replaces inScimRequestScope() must be at least as narrow — a /scim/v2-scoped signal, not "transactions on by default".
  2. Land the runtime pin the card asks for. credential-at-rest-posture.test.ts's note that the vendor refuses to mount on a sequential-fallback transaction is a mount-time assertion and it passed while this was broken. Add the observation the card names: a SCIM mutation observed to call engine.transaction at least once. A fix without that pin can regress silently the same way.
  3. [finding] SCIM active:false no longer disables the account — the vendor ban coupling was removed upstream in @better-auth/scim 1.7.0 and nothing in this repo replaced it #14360's suite already pins the residualscim-deactivation-reconcile-user.test.ts, face (c). Flip that assertion deliberately and say so in the changeset; do not let it look like an incidental test edit.
  4. The card's two unmeasured items stay unmeasured unless cheap: whether the scope propagated on 1.7.0-rc.1 (may well have — the #3653 note may have been true when written, and saying so is fairer than implying it was always wrong), and Postgres/MySQL. The mechanism is adapter-side, so the sqlite reading should carry; note it rather than re-running the matrix.

How each is met: (1) the door scope is keyed on the endpoint path prefix /scim/v2 — the predicate the vendor's own after-hook matcher uses — and nothing else changed in the adapter's conditional; pin (c) below measures sign-up + sign-in at ZERO engine.transaction calls, and the FULL plugin-auth suite is green. (2) scim-transaction-scope.test.ts (a) observes POST /Users and PATCH /Users/{id} each calling engine.transaction ≥ 1 and driver.beginTransaction ≥ 1, with every identity write sampled INSIDE the scope. (3) line 487 flipped falsetrue with a rewritten comment; the changeset names the flip. (4) both items recorded as NOT MEASURED below.

Step 0 — base

git merge-base --is-ancestor 21c7dbe76b7d44df32fbd9f6497db6372b9f2d0f origin/main exits 1, and that is the squash, not a missing landing: PR #14540 landed as 7303cbf4d ("… (#14360) (#14540)") on origin/main, and the file to flip is on origin/main with a blob byte-identical to the PR head's (git rev-parse of scim-deactivation-reconcile-user.test.ts at 21c7dbe76 and at origin/main both 8a4d445f4ed811c9d257e211910dffe9f96ebae0; git diff --stat between them exits 0 printing nothing). Worktree cut from origin/main f60ab90ae (BASE), not from the PR branch; origin/main (2a2653619) merged in afterwards as ec7278259 (clean, no regen-pending, plugin-auth untouched on main in that range).

Premise checks (on origin/main before any edit)

  • P1 holds. objectql-adapter.ts:829 if (!inScimRequestScope()) return cb(wrappedAdapter as never); (:8 imports it); auth-manager.ts:3279 scimRequestScope.enterWith({ scim: true }); inside the verifyBearerToken callback (:3269 destructures it from the dynamic import); scim-connection-service.ts:63 the ALS, :66-67 the reader. The adapter still reads the ALS; the stamp still lives in the verifier.
  • P2 holds — reproduced before editing, kept as the red half. scim-transaction-scope.test.ts on the unfixed tree (f60ab90ae + the test file only): engine.transaction 0 calls on POST /scim/v2/Users, writes sampled insert:sys_scim_connection_binding:scim insert:sys_user:NO-SCOPE insert:sys_scim_subject:NO-SCOPE insert:sys_scim_user:NO-SCOPE update:sys_scim_subject:NO-SCOPE update:sys_scim_connection_binding:NO-SCOPE; 0 calls on PATCH /scim/v2/Users/{id} active:false, writes update:sys_scim_subject:NO-SCOPE update:sys_user:NO-SCOPE update:sys_scim_user:NO-SCOPE update:sys_user:NO-SCOPE update:sys_scim_connection_binding:NO-SCOPE; the (b) atomicity case: sys_user survived the failed provisioning. Both requests answered 201 / 200. Note the one scim-scoped write: the vendor's connection-binding insert, made by the auth middleware right after await verifyBearerToken() — a DESCENDANT of the stamp — is the only write that saw it, which is exactly the enterWith shape.
  • P3 holds. face (c) line 487 read expect(await scimActive(h, owner.scimId)).toBe(false); under the comment block naming this card; line 501 is the positive control's false.
  • P4 holds. Installed @better-auth/scim 1.7.2 / better-auth 1.7.2 / @better-auth/core 1.7.2 (pnpm store node_modules/.pnpm/@better-auth+scim@1.7.2_…). @better-auth/scim/dist/index.mjs:4002 runIdentityMutationTransaction:4011 runWithTransaction(adapter, async () => callback(await getCurrentAdapter(adapter))) (imported from @better-auth/core/context, :5); @better-auth/core/dist/context/transaction.mjs runWithTransactionadapter.transaction(async (trx) => als.run({ adapter: trx, pendingHooks, isTransactionActive: true }, fn)). The three SCIM mutation sites: :6527 (POST /Users), :6749, :6839 (PATCH / PUT). assertNativeSCIMTransactions at :5499 asks only typeof adapter.options?.adapterConfig.transaction === "function".
  • P5 holds. packages/spec/** and content/docs/releases/** untouched (diff file list above).

Hypotheses (by measurement)

  • H1 holds — mechanism measured, boundary named. A throwaway probe (deleted before commit) drove the installed better-auth 1.7.2 directly: betterAuth({...}) with a global hooks.before doing alsA.enterWith(...), a plugin endpoint whose use middleware does alsB.enterWith(...) (the verifier's shape), the request issued inside alsC.run(...), and a fourth ALS opened at the door and MUTATED from hooks.before; the handler read all four through a nested als.run (the shape runWithTransaction uses). Reading: beforeHookEnterWith: null, useMiddlewareEnterWith: null, doorRun: { via: "door run" }, beforeHookMutatedDoorStore: { flag: true }, endpointContextPath: "/probe/scope". So the boundary is the await of the middleware chain in the endpoint frame — better-call's endpoint runner awaits the use middlewares (the verifier's caller) and dispatchAuthEndpoint awaits runBeforeHooks(...) (better-auth/dist/api/dispatch.mjs:205-228, which then re-enters the handler under a second runWithEndpointContext) — from continuations captured BEFORE the stamp. An enterWith marks the async resource it runs in and that resource's descendants only.
  • H2 holds — remedy picked by measurement. Candidate (a) as literally proposed (enterWith in hooks.before) is REFUTED by the probe (null). Candidate (b) — reading the vendor's endpoint context at write time — is VIABLE (tryGetCurrentAuthEndpointContext()?.path reads /probe/scope through the nested als.run; @better-auth/core/context exports it, and session-tombstone.ts:231 already imports from that module), but it retires scimRequestScope / inScimRequestScope, which the dispatch declares an export removal (Clause-② yes + stop), and couples the adapter to the endpoint-context API. Chosen: the run-scoped stamp at the door (doorRun visible; beforeHookMutatedDoorStore shows the same run-at-the-door mechanism the actor-attribution scope relies on). It is the house pattern for both a request-scoped ALS and endpoint-path-keyed atomicity, keeps both symbols in use, and is exactly as narrow: SCIM protocol requests only.
  • H3 holds. Green half on the fixed tree: scim-transaction-scope.test.ts 5/5 and scim-deactivation-reconcile-user.test.ts 11/11 (Test Files 2 passed (2) · Tests 16 passed (16)), including the flipped face (c) — the refused last-administrator deactivation now leaves the SCIM resource reading active: true while the positive control's false on line 501 still passes.
  • H4 holds. Pin (c): sign-up + sign-in through handleRequest write sys_user / sys_account / sys_session with engine.transaction 0 and driver.beginTransaction 0, no write in the SCIM scope. FULL @objectstack/plugin-auth suite: pnpm --filter @objectstack/plugin-auth test (the package's vitest run, VITEST_MAX_WORKERS from scripts/vitest-worker-cap.mjs) on ec7278259Test Files 91 passed (91) · Tests 1835 passed (1835), VERDICT command-exit 0 (held the lock 155 s). The six SCIM-adjacent siblings the dispatch names (credential-at-rest-posture, scim-case-insensitive-identifier, better-auth-schema-parity, last-admin-guard, managed-extension-fields, auth-manager) are inside that run. The two measured breakages the #3653 note records (sign-up 500s on the memory engine; the 180 s hook-timeout deadlock on single-connection sqlite) did not return.
  • H5 holds. git diff -U0 origin/main...HEAD | grep -E '^[+-].*\bexport\b' → nothing (grep exit 1). Clause-② no. Changeset patch for @objectstack/plugin-auth.

Tests (head ec7278259)

  • RED half (unfixed tree): pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/scim-transaction-scope.test.ts …Tests 3 failed | 3 passed (6) — (a) POST, (a) PATCH, (b) red with the readings quoted under P2; (c), (d) green (they measure the unchanged non-SCIM posture); VERDICT command-exit 1.
  • GREEN half: … src/scim-transaction-scope.test.ts src/scim-deactivation-reconcile-user.test.tsTest Files 2 passed (2) · Tests 16 passed (16), VERDICT command-exit 0.
  • pnpm --filter @objectstack/plugin-auth typecheck (src + tsconfig.examples.json + check:test-typecheck) → check:test-typecheck: OK — … 10 file(s) / 94 error(s) / 23 pinned signature(s) held in test-typecheck-debt.json (ledger unchanged), VERDICT command-exit 0. (A first run failed only on examples/basic-usage.ts resolving the package's own unbuilt dist/ — PREREQUISITE NOT MET, cleared by pnpm --filter @objectstack/plugin-auth build.)
  • FULL suite: pnpm --filter @objectstack/plugin-auth test (the package's vitest run, VITEST_MAX_WORKERS from scripts/vitest-worker-cap.mjs) on ec7278259Test Files 91 passed (91) · Tests 1835 passed (1835), VERDICT command-exit 0 (held the lock 155 s). The six SCIM-adjacent siblings the dispatch names (credential-at-rest-posture, scim-case-insensitive-identifier, better-auth-schema-parity, last-admin-guard, managed-extension-fields, auth-manager) are inside that run. The two measured breakages the #3653 note records (sign-up 500s on the memory engine; the 180 s hook-timeout deadlock on single-connection sqlite) did not return.
  • pnpm check:system-context-censusOK — 109 elevation read sites in 20 packages across 45 files, all anchored; no re-anchor of content/docs/permissions/system-context.mdx needed (working tree clean after the gate).
  • pnpm check:nul-bytesOK (scanned 7980 text file(s) … no raw ASCII control bytes); grep -naP control-byte self-scan over the seven touched files: 0 hits.

Ablation (committed tree)

Mutation = the OLD mechanism restored on the committed tree ec7278259, in auth-manager.ts only: the door scope disabled (const runRequest = (false as boolean) && isScimProtocolPath(endpointPath)), and scimRequestScope.enterWith({ scim: true }) put back as the first line of the verifyBearerToken callback (with its destructure). Applied by perl -0pi, proven on disk by anchored counts, never by the editor's exit code: ABLATION-14522-door=1 (expect 1) · ABLATION-14522-import=1 (expect 1) · ABLATION-14522-enterWith=1 (expect 1) · original-door-line=0 (expect 0) · enterWith-total=2 (expect 1) — the 2 is the marker line plus the comment line "⛔ No scimRequestScope.enterWith(...) here", i.e. one real call; pre-mutation blob 4e1bede81930d4e97cf9aca641a4036d79bd63a3 = HEAD blob, post-mutation blob 7165d4eefaac562a1633f185d640374047d689f1. No build leg: the subject (AuthManager) is imported by both test files relatively from src (./auth-manager.js), not through a package exports map, so no dist/ is on the resolution path and ablation-dist-preflight does not apply.

Reading (predicted direction: pins (a)/(b) and #14360 face (c) red, (c)/(d) green): Test Files 2 failed (2) · Tests 4 failed | 12 passed (16) — red: (a) POST /Users …, (a) PATCH /Users/{id} active:false …, (b) a failure on the sys_scim_user write rolls … back, and (c) the IdP gets a 403 SCIM error naming the invariant, and the account stays active (the flipped line 487 reads false again under the old mechanism); green: (c) sign-up/sign-in zero transactions, (d) SCIM read, and the other ten #14360 cases. ABLATION vitest exit: 1; VERDICT command-exit 0 (the wrapper's own exit is the script's, which restores and exits 0).

Restore = git checkout HEAD -- ABS_PATH in trap … EXIT INT TERM, proven: RESTORE: hash-object=4e1bede81930d4e97cf9aca641a4036d79bd63a3 HEAD-blob=4e1bede81930d4e97cf9aca641a4036d79bd63a3 MATCH · git diff HEAD --stat: [] · porcelain: [].

Gates (derived on the final head with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, no paths passed)

Derived at ec7278259 (the derivation's own stderr line: "gate list derived from the tree of 'objectstack-ai/objectstack' at commit ec72782"; it notes the tree is 10 commits behind origin/main 2514d49f3 — post-merge landings, none under plugin-auth, and the merge-tree against that origin/main is clean). The list is byte-identical to the pre-merge derivation on a88c952fd (36 commands). Run unlocked in the foreground in batches under the ~10-minute cap — the lock script's own header classes check:* gates as unlocked sibling work, and the lock was saturated by a sibling's gate batches (two 540 s queue timeouts on the full-suite call before it acquired) — with each command's exit captured to a results file BEFORE any pipe. Batch 2 was cap-killed (exit 143) after scripts/pm/check-half-states.mjs alone took 517 s; the two commands it left unrun (check:engine-double-contract, check:logger-receiver-detach) were re-run in a fourth call.

  • 33 of 36 exit 0: check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-comment-mask-adoption, check-cross-package-test-inputs (node and pnpm spellings), check-empty-changeset, check-keyed-text-bounds, check-plugin-teardown-shape, check-shard-attestation, check-system-context-census (OK — 109 elevation read sites in 20 packages across 45 files, all anchored), check-tenant-audit-census, check-undeclared-dep-imports, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/check-half-states (report-only patrol; its 155 half-state(s) found is a board census, not a verdict on this PR), pm/release-rehearsal-clone --self-test, check:changeset-gate-self-tests, check:dispatcher-error-vocabulary, check:doc-authoring, check:engine-double-contract, check:logger-receiver-detach, check:objectql-double-limit, check:objectui-changeset, check:page-declaration-shape, check:pm-half-states, check:published-files, check:query-options-erasure, check:slot-lookup, check:test-source-alias, check:type-check-coverage (OK — 69/79 workspace packages type-checked (plus the root), 10 in the DEBT ledger), check:type-source-resolution, check:where-matcher.
  • 3 NOT MEASURED, each declared by the gate itself with exit 3 (its prerequisite code, distinct from a finding's 1): check-test-completeness.mjs ("PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named … record this gate as NOT MEASURED"); check:dual-build-cjs-loads ("PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ … Run pnpm build first. ⛔ This is NOT a pass: nothing was measured"); check:type-check-debt ("--re-measure cannot run: 29 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk … Build the closure first, exactly as lint.yml does"). A whole-workspace build is a repo-level run this container cannot fit under the cap; CI owns all three.
  • 0 red.

pnpm lint (eslint . --no-inline-config, repo-wide) is CI's run; the local reading is a PROVEN narrowing, three pieces: ① the population is read from eslint.config.mjs itself — its own comment at line 328 states "(no parserOptions.project, no typed @typescript-eslint rules)", and every parserOptions block in the file carries only ecmaVersion / sourceType, so linting is not type-aware; ② pnpm exec eslint --no-inline-config --format json over the six touched .ts files → files linted: 6 | errors: 0 | warnings: 0, exit 0; ③ invariance: with no type-aware rules a file's verdict depends only on its own bytes, so this diff cannot move the verdict of any untouched file — the narrowing excludes nothing.

NOT MEASURED (scope guard 4)

  • Whether the scope propagated on @better-auth/scim 1.7.0-rc.1 — not re-measured. The #3653 note may well have been true when written (a different dispatch shape); the rewritten prose says the stamp "never reached this seam" on 1.7.2 and does not claim it never did.
  • Postgres / MySQL — measured on better-sqlite3 :memory: only. The mechanism is adapter-side (where the ALS store is read), not driver-side, so the reading should carry; driver.beginTransaction ≥ 1 is pinned on sqlite only.

Log levels

No new log site at any level. The vendor's [Better Auth]: back-channel logout planning failed … no such table: sys_oauth_access_token ERROR seen in the suite output is pre-existing harness noise (the SCIM harnesses register no OAuth objects), filed separately.

Out of scope

Filed as #14615 (finding, unassigned, after a targeted MCP search_issues dedup — four non-matching closed hits — with a positive control that returned #14522): the plugin-auth SCIM harnesses (the #14360 suite and, by copying its shape, the new pin) register no OAuth objects, so every driven sign-in prints a Better Auth ERROR back-channel logout planning failed … no such table: sys_oauth_access_token. Green-run noise, not a failure; not touched here.

Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…M provisioning runs inside one engine transaction

The adapter's `transaction` config opens a real `engine.transaction()` only
while `inScimRequestScope()` reads true. The scope was stamped with
`AsyncLocalStorage.enterWith` inside the `verifyBearerToken` callback handed
to `@better-auth/scim`, and on 1.7.2 the store never reached the writes: an
`enterWith` marks only the async resource it runs in and its descendants,
and the vendor resumes the endpoint handler from a continuation captured
before the verifier ran. Measured: zero `engine.transaction` and zero
`driver.beginTransaction` calls across POST /Users + PATCH /Users/{id},
`inScimRequestScope()` false inside every sys_user / sys_scim_user write.

`AuthManager.handleRequest` now opens the scope with `run(...)` around every
request whose better-auth endpoint path is under `/scim/v2` — the same seam
the actor-attribution scope and the subject-erasure transaction use, and
exactly as narrow as before: non-SCIM flows keep their sequential posture.

A refused last-administrator deactivation now rolls the vendor's own
`scimUser.active = false` write back; the #14360 suite's face (c) pin on that
residual is flipped from `false` to `true` deliberately. A new runtime pin
(`scim-transaction-scope.test.ts`) observes each SCIM mutation calling
`engine.transaction`, a failed provisioning leaving no partial identity, and
sign-up/sign-in opening zero transactions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth, touching 8 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/user-ban-write.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 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), handleRequest (symbol, a method of class AuthManager))
  • content/docs/kernel/services-checklist.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AuthManager (symbol, a top-level class), createObjectQLAdapterFactory (symbol, a top-level function), handleRequest (symbol, a method of class AuthManager))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/user-ban-write.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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 — 11 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 9d7f7259fa96bd79138d3e55c2801886f56221bbpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 3700abebe9135748c5eabe9d57041eefdf9c72a3 — the merge of head ec72782592f9b2af872b8aa0c9600ef3bb6ab0ce into base 9d7f7259fa96bd79138d3e55c2801886f56221bb, 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 3700abebe9135748c5eabe9d57041eefdf9c72a3 && git checkout 3700abebe9135748c5eabe9d57041eefdf9c72a3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9d7f7259fa96bd79138d3e55c2801886f56221bb ec72782592f9b2af872b8aa0c9600ef3bb6ab0ce && git checkout -B drift-repro 9d7f7259fa96bd79138d3e55c2801886f56221bb && git merge --no-ff ec72782592f9b2af872b8aa0c9600ef3bb6ab0ce

node scripts/docs-audit/affected-docs.mjs --json 9d7f7259fa96bd79138d3e55c2801886f56221bb

⚠️ 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 9d7f7259fa96bd79138d3e55c2801886f56221bb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Landing provenance — ready + auto-merge at head ec7278259


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/l tests tooling

Projects

None yet

2 participants