Skip to content

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

Draft
hotlong wants to merge 9 commits into
mainfrom
claude/issue-16130-open-org-scoping
Draft

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
hotlong wants to merge 9 commits into
mainfrom
claude/issue-16130-open-org-scoping

Conversation

@hotlong

@hotlong hotlong commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Part of #16130 — phase 2, the objectstack half only. The cloud half (bump the framework pin, strip what moved, subclass, keep the gate) is a separate dispatch and is not touched here.

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, do not queue.

What this delivers

The org-scoping registrar, in open core. Everything else the maintainer's goal needs was already open — the Layer 0 wall, the three postures, the posture knob, the organization and invitation objects, better-auth's organization plugin in plugin-auth, and the requiresService: 'org-scoping' Setup gates. The registrar was the one missing piece, and without it an open install that set OS_TENANCY_POSTURE=isolated could not enforce it: its only route forward was OS_ALLOW_DEGRADED_TENANCY=1, the wall configured but not enforced.

New package packages/plugins/organizations@objectstack/organizations, Apache-2.0, carrying the 1300 lines comment 5556477998 classified as moving. The 306-line licence gate stays in cloud.

Package name — measured, not assumed

The name is @objectstack/organizations, the spelling the open CLI's ORGANIZATIONS_RUNTIME_PKG already carries, so the loader needs zero changes. Two readings behind that:

  • npm. npm view @objectstack/organizations returns E404 — never published, so the name is free. (For contrast, the historical @objectstack/plugin-org-scoping is on npm at 12.6.0, owned by the org, not deprecated and with no per-version deprecations — recorded here because the earlier proposal would have resumed it at 17.x with a gap.)
  • Live config carrying the old name. 24 files mention plugin-org-scoping; all but four are CHANGELOGs. The four are ADR-0056 / ADR-0057 consumer lists, plugin-security's README (corrected below), and three fixtures inside scripts/check-published-readme-exports.mjs whose workspace maps are hand-supplied, so none of them resolves against the real tree. Nothing live points at the old name.

Where the package sits, and why — packages/plugins/organizations

Raised in review as possibly belonging at top level (packages/organizations/), mirroring cloud's layout. Kept under packages/plugins/ on the record and on measurement. Stating the evidence so the maintainer can overrule it at hand-merge with all of it in view:

  • One CI step would stop covering it. .github/workflows/ci.yml"Verify capability packages ship a runtime entry (no dts-only / half-built)" — enumerates exactly packages/triggers/* packages/services/* packages/drivers/* packages/plugins/*, and it is the only such enumeration in that file; top-level packages/* has no equivalent. Its own comment says why it exists: "Capability packages (services / triggers / plugins) are loaded by the multi-tenant runtime via a DYNAMIC import of their published entry", and a half-built dist means the capability silently fails to load. That is this package exactly — serve reaches it through importFromHost, a dynamic import of the published entry — and a silent failure to load is precisely the "wall configured but not enforced" outcome this whole card exists to end. This package qualifies for that step today (build script, main: dist/index.js).
  • The record still points here. AGENTS.md's Monorepo Layout lists plugins/ # Official plugins & drivers, and its Context Routing table routes packages/plugins/**Plugin Developer · Implements spec contracts. A repo-wide search finds no recorded retirement of that directory.
  • A maintainer-approved ruling names it as a live family five days ago. [finding] No plugin package has a tsc program that compiles its tests — plugin-approvals alone hides 345 type errors, and 0 of 14 are in the test-typecheck ledger #14062's director ruling of 2026-09-01 (maintainer verbatim: 「同意」) onboarded "the packages/plugins/** family" into the check:test-typecheck instrument family. This package's tsconfig.test.json follows that family's shape and says so.
  • The de facto convention agrees. Measured by first-commit date of each package manifest: the most recent new package anywhere in this repo before this one went into packages/plugins/ (plugin-pinyin-search, 2026-07-16) — later than the last new top-level package (sdui-parser, 2026-06-29).
  • It is a Plugin. It implements the Plugin interface and is mounted with kernel.use(). Three of the fifteen entries under packages/plugins/ already carry no plugin- prefix (knowledge-memory, knowledge-ragflow, embedder-openai), so organizations@objectstack/organizations matches the directory-basename convention there.
  • The symmetry argument is satisfied by the name, not the path. The maintainer's instruction was 「可以直接用cloud 下的 organizations 包名吗」 — 包名, the package name, which this PR takes verbatim. That is also the only thing the subclass coupling reads. Cloud's tree is flat throughout (packages/organizations, packages/service-cloud, packages/security-enterprise), so mirroring its layout would mean adopting a convention this repo does not use.

⚠️ If the retirement of packages/plugins/ is a real maintainer ruling rather than an inference, this is a one-command move — but it should land with the ci.yml enumeration widened in the same change, or the runtime-entry guard is lost for the one package whose whole contract is being dynamically imported.

⭐ The shared name, and how the entitlement stays mounted on EE

The commercial repo keeps a private package of the same name. That is the mechanism, not a collision — but it is also the sharpest hazard in this card, so it was measured rather than reasoned:

EE mount path how it resolves class mounted
objectstack serve importFromHost('@objectstack/organizations')createRequire(join(hostRoot,'package.json')), gated on the served app's own declaration (#4719) cloud's gated subclass
artifact-kernel-factory.ts a bare import('@objectstack/organizations') from inside packages/objectos-runtime, which declares it cloud's gated subclass
service-cloud's control-plane preset lazyPlugin('com.objectstack.organizations', ...) reaching control-plane-organizations.js, in a package that declares it cloud's gated subclass

All six cloud manifests that declare the name declare it workspace:*apps/cloud, apps/ee-group-showcase, apps/ee-tenant-crm-showcase, apps/objectos-ee, packages/objectos-runtime, packages/service-cloud — and cloud's lockfile resolves every one to link: its local package. The name appears three more times in cloud outside a dependency field, and none of the three is a dependency resolution: the package's own name, security-enterprise's objectstack.preflightAlsoReads array entry, and a paths alias in that package's tsconfig.test.json. (Independently re-verified in review, which reached the same six and the same exceptions.) pnpm's workspace: protocol resolves only to the local workspace package — it cannot fall through to the registry, and a missing one fails the install rather than substituting silently. So the class an EE deployment mounts is decided by the manifest that declares the name, on all three paths, with no change to the open package's shape and no change to the loader.

What the cloud half must preserve (stated now because it constrains that PR): keep the package name, keep it private: true, and keep every host declaration at workspace:* — never a semver range.

What could break it, and is therefore forbidden here: a framework package taking @objectstack/organizations as its own dependency. Cloud consumes the framework by link:, so that would put the ungated copy inside the tree an EE app links, reachable by a bare import that never consults the app's manifest — the entitlement bypassed by resolution rather than by any defect in the gate. Apps declare this package; packages do not. src/no-framework-dependents.pin.test.ts holds it mechanically, with an anti-vacuity control on the population and a control proving the detector can find a planted declaration.

The three hard boundaries

  1. multi-org-license-gate.ts stays in cloud. Not moved, not reimplemented, and no hook left for it. Its 306 lines and 12 exported symbols are untouched.
  2. The multi-node gate carrier stays. MULTI_NODE_GATE_CARRIER_PACKAGES is unedited and the open package acquires neither the obligation nor the security-enterprise import that discharges it. ⚠️ One diagnostic changes for an open install and is recorded in that file's header: the carrier import used to fail (unavailable) and now succeeds while registering nothing (loaded-without-gate). Both leave no gate registered, so the fail-closed default refuses a multi-node verdict exactly as before.
  3. No licence check of any kind. The constructor's assertMultiOrgEntitled() and its import are the 18 deleted lines. Per Q1 → A, cloud subclasses and calls its gate in its own constructor; the open class takes no entitlement callback, hook, option or protected seam, and new X.OrganizationsPlugin() keeps working for cloud's two call sites. A ⛔ comment sits where the call was, naming what may not be added there.

Q2 — the two comment blocks

  • ~128-130 (ADR-0105 D12's "belongs here, in the commercial runtime"): reworded to record that the open package entitles both walled postures by construction. The declaration ['group','isolated'] moves as-is.
  • ~134-136 ("gating it behind a licence flag … becomes a one-line edit HERE"): reworded, as required. It is replaced by a ⛔ that says the opposite in the same place — that this file and this package carry no licence check, offer no hook for one, and take no entitlement callback.

The service name

org-scoping is unchanged, and so is the plugin id com.objectstack.organizations. Both are anchored by open consumers and by cloud fixtures; the "kept on purpose" comment moves with the registration.

The ADR amendment is in this PR

  • ADR-0132 (new) — the multi-organization runtime is open core; only the entitlement stays commercial. Carries a Provenance section naming cloud ADR-0081 and its date, the D3 resolution mechanism, and the boundaries the code comments cite by number.
  • ADR-0105 D12 — amended in place: the code-vs-activation split stands, the "closed-source" sentences read as history, and D12's argument for putting supportedPostures in the commercial runtime does not carry to the open package. Its status line records the amendment.
  • The cloud record's one-line pointer back to ADR-0132 is owed by the cloud PR, not this one.

#14508 as found: still pm:queue, unassigned, no branch, no PR, last touched 2026-09-04 by triage. I am the first writer. Its scope is cloud ADR-0081 D1/D3/D4 (the open basics half) and its Shape section says D2 "stays in cloud" — ADR-0132 is what makes that one line stale. ADR-0132 says so explicitly and asks #14508's writer to cite this number rather than open a competing record; a comment on #14508 says the same.

Dispositions changed from the phase-1 classification

Comment 5556477998 is the source of truth for what moves. Six changes, every one forced by a gate the open tree runs and cloud's does not — none of them changes what the code does:

file classified changed to why
claim-orphan-org-rows.test.ts, claim-org-seed-ownership.test.ts move as-is move with changes check:engine-double-contract — the fakes' update() now opens with assertEngineUpdateDispatch
org-creation-no-cross-org-copy.test.ts grant-strip only + the same assertion same gate
walled-default-org-self-registrant.pin.test.ts grant-strip only + the WHERE double REFUSES a combinator check:where-matcher — it read $and/$or as a field name, so a filter it cannot express answered "no rows", which every negative assertion in that file would have read as evidence
organizations-plugin.ts 480 as-is 3 more lines changed check:slot-lookup — the objectql / metadata lookups are typed structurally instead of erased to any; that ratchet's baseline never grows
organizations-plugin.ts, membership-policy-gate.ts prose lines listed + 4 runtime strings check:doc-authoring — tracker ids leave a rendered boot refusal and a log line, and move to adjacent comments
every ADR-0081 citation not flagged cloud ADR-0081 a bare ADR-0081 resolves against this repo's ADR-0081 (the React page tier) — the exact collision ADR-0105's own citation note documents

The three Apache-2.0 files move byte-identical — verified by git hash-object against cloud origin/main:packages/organizations/src/* (08eb3641…, 3b8d03f6…, da45ee8f… and the two as-is test files), not by an insertion count.

Consequence edits — claims this PR falsifies

Three places in the open tree assert something that stops being true on merge. Each is corrected here rather than deferred, and none changes behaviour:

  • packages/plugins/plugin-security/README.md — said "No such package exists — not on npm, and in no directory of this repo." It is a published README (the page npm renders). Now states the package is Apache-2.0 and in this repository, and how an EE deployment resolves the same name to a gated subclass.
  • packages/spec/src/kernel/platform-capabilities.ts — the roster row's note said "closed-source … not on the public npm registry". Corrected. ⛔ edition: 'enterprise' and the row key are untouched: the row records the entitled variant, and the carrier list's drift test reads that field.
  • packages/services/service-cluster/src/multi-node-gate-mount.ts — the carrier header now records the one-name-two-packages fact and the unavailableloaded-without-gate diagnostic change, with a ⛔ against "repairing" it.

Plus the gate-driven bookkeeping a new package owes: the isSystem census row for the organization_id auto-stamp's elevation short-circuit (row 62, with the page's counts moved), the boot-refusal row for WALLED_MEMBERSHIP_POLICY_UNDECLARED, the check:cross-package-test-inputs radius plus its turbo.json task for the D3 pin, llms.txt 68 → 69, and the changeset fixed group.

Not edited, deliberately: packages/verify/src/harness.ts's "requires the enterprise @objectstack/organizations package" message and packages/cli's serve / doctor resolution. Teaching those about an open registrar is #16137's, and doing it here would be the wiring this PR explicitly does not claim.

⛔ This PR does not claim the acceptance criterion

An open-only install with OS_TENANCY_POSTURE=isolated and OS_ALLOW_DEGRADED_TENANCY unset, booting with the wall ACTIVE and enforcing the matrix, is not measured here and is not claimed. objectstack serve still resolves the runtime from the served app's own declaration and is not wired to mount this package off the posture. That acceptance is discharged by #16137, which is blocked on this — per the PM's Q3 answer it is "run the existing open matrices (single-kernel-isolated-api-key-matrix.test.ts, single-kernel-isolated-session-org-claim-matrix.test.ts) against the real registrar instead of their hand-written posture: 'isolated' stub", not "build a walled showcase app".

Verification

Declared narrowing — verification ran UNLOCKED. scripts/pm/os-verify-lock.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is util-linux, and a stock macOS does not ship it), so every command below was run through that entry point without the lock — a declared narrowing, not a silent one. No serialization guarantee held for these runs, nor for any sibling agent in this container while they ran.

Gate set derived mechanically — node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, the machine-readable mode, never the human-readable output. 131 commands, run at 11ca403f6 — the final commit, after the SECOND origin/main merge — 125 green. (Prior heads for comparison: 6d7d0e4c9 124 green, 494cb77f7 124 green, same split each time.) The six that are not green are all NOT MEASURED, none is a finding, and each is named with its reason:

gate exit verdict
check:dual-build-cjs-loads 3 PREREQUISITE NOT MET — its own words. 11 packages have no dist/ in this tree.
check:i18n 3 PREREQUISITE NOT MET — needs the CLI's extract-config build closure.
check:type-check-debt 3 PREREQUISITE NOT MET — nothing measured.
@objectstack/spec check:skill-examples 1 PREREQUISITEpackages/client-react/dist holds no .d.ts; the gate refuses rather than compute a false green.
check:merge-driver 1 PRE-EXISTING, proven. check-regen-pending.mjs --self-test fails identically in a clean worktree at origin/main 9f3989771 (case: "a gate whose RUNNER is not installed"). Baseline captured, worktree removed.
check:bash32-floor 1 HOST. Its probes need a bash that cannot parse coproc / ;;& / [[ -v ]]; this host's /bin/bash is 3.2 and cannot even parse the probe text. Zero shell files in this diff.

CI builds fresh on Linux, so all six are measured there. ⭐ check:dts-closure was the seventh on the previous head and is green at this one: it had named @objectstack/embedder-openai, and building the dependency closure supplied its declarations — which confirms it was a prerequisite of this checkout rather than a finding about the diff.

origin/main was merged into this branch twice (the PR read mergeable_state: dirty on creation, and a conflicting PR gets no CI at all). The merge brought packages/spec, so per AGENTS.md §10 it was rebuilt and check:generated re-run — All 15 generated artifacts are up to date. The merge driver also deferred content/docs/permissions/system-context.mdx, which is a merge=os-regen artifact git ran no text merge on; .githooks/pre-push refused the push until it was regenerated from the merged tree, and that regeneration is its own commit. It rewrote anchor lines only — 18 file.ts:line citations the incoming commits moved — with row 62, the section extents and every declared count unchanged. The whole gate set, the build closure, typecheck and the 76 tests were then re-run on the merged head.

Second round, same two traps, same handling. main moved again and the PR went dirty; a conflicting PR gets no fresh CI, so a green badge on it is a false read. The merge was append-only — no rebase, no force-push, no squash — and clean, with zero conflicts. ⭐ main has never touched packages/plugins/organizations, so the classification's premise did not move under this card; the three files both sides touched were system-context.mdx, dispatcher-error-vocabulary.ts and engine-double-contract.pinned.json, and my content survived in all three (row 62, the boot-refusal row, the three pinned fakes). packages/spec came in again, so it was rebuilt and check:generated re-run — All 15 generated artifacts are up to date. The merge=os-regen deferral fired again and .githooks/pre-push refused until the census was regenerated from the merged tree; that regeneration is its own commit and rewrote anchor lines only — 15 this time, proven rather than asserted: diffing the page against its pre-regeneration copy and filtering out anchor-bearing lines leaves nothing, row 62 is byte-identical, and the entire declared-counts table is byte-identical (106 sites / 20 packages / 45 files; rows 1–62 behaviour, rows 63–66 carry-onward). Every number in this section is from 11ca403f6; none is carried forward.

Package-level, all green at 11ca403f6:

  • pnpm --filter '@objectstack/organizations^...' build — dependency closure first, in the new worktree.
  • pnpm --filter @objectstack/organizations buildcheck-dts-emitted: 1/1 declared declaration file(s) present.
  • pnpm --filter @objectstack/organizations run typechecktsc --noEmit clean and check:test-typecheck: OK — 0 file(s) / 0 error(s). ⚠️ Those are two different programs: the build config excludes **/*.test.ts, so "typecheck clean" alone would say nothing about the test layer. tsc --listFiles -p tsconfig.test.json puts all 14 files (7 source + 7 test) in the program — measured, not assumed. Zero residue, so the package ships no test-typecheck-debt.json and no gen: script, matching the ten zero-residue plugin packages rather than the four that carry ledgers.
  • pnpm --filter @objectstack/organizations test7 files, 76 tests, all passing.
  • The five CLI pins the classification flagged as at-risk under the shared name — serve-cluster-host-resolution, serve-organizations-message-spelling, doctor-organizations-message-spelling, serve-organizations-host-resolution, test/serve-capability-vocabulary67 tests, all passing, on a built closure. packages/spec's platform-capabilities.test.ts: 27 passing.

Imports re-verified rather than trusted. Every non-local specifier the moving code names was resolved against this tree: Plugin / PluginContext and resetPlatformAdminEmailMemo from @objectstack/core; isDefaultOrganizationBootstrapTrigger, ensureDefaultOrganization, EnsureDefaultOrganizationResult, isMembershipPolicy, MEMBERSHIP_POLICIES from @objectstack/plugin-auth (all reached through its export * barrel); resolveTenancyPosture from @objectstack/types; ServiceObject from @objectstack/spec/data. Nothing failed to resolve, and nothing needed a shim.

And the verdicts are about the source, not the last build. tsconfig.json carries paths for all five workspace dependencies and vitest.config.ts carries anchored-regex aliases for the three the tests reach as values — so check:type-source-resolution and check:test-source-alias both pass with this package resolving source, rather than the package being registered in either gate's shrink-only unaliased ledger.

hotlong and others added 5 commits September 6, 2026 13:03
…ore (#16130)

Creates packages/plugins/organizations — @objectstack/organizations — carrying
the 1300 lines cloud's packages/organizations classified as moving. The licence
gate (306 lines) stays in the commercial repo, which will subclass this class
and call it in its own constructor.

WIP: ADR-0132 and the changeset still to come.

Claude-Session: https://claude.ai/code/session_6679d191-11f4-465b-b322-0e0409d76793
Co-authored-by: Claude <noreply@anthropic.com>
…6130)

Amends ADR-0105 D12 in place, corrects the three open-tree claims the move
falsifies (plugin-security README, the spec roster row, the multi-node carrier
header), and adds the changeset.

Claude-Session: https://claude.ai/code/session_6679d191-11f4-465b-b322-0e0409d76793
Co-authored-by: Claude <noreply@anthropic.com>
…never met (#16130)

The moved code arrives in a repo with gates cloud's does not run. Each finding
was a real one, fixed rather than baselined:

- slot-lookup: the `objectql` / `metadata` lookups are typed structurally
  instead of erased to `any` — that ratchet's baseline never grows.
- engine-double-contract: the three moved test fakes open `update()` with
  `assertEngineUpdateDispatch`, pinning them to the producer's rejection.
- doc-authoring: tracker ids leave the rendered boot refusal and the log line;
  an operator cannot resolve `#NNNN`.
- dispatcher-error-vocabulary: `WALLED_MEMBERSHIP_POLICY_UNDECLARED` is
  recorded as a boot-refusal, the class its neighbours are ruled by.
- system-context census: the `organization_id` auto-stamp's elevation
  short-circuit gets its row (62) and the page's counts move with it.
- cross-package-test-inputs + turbo: the ADR-0132 D3 pin declares its radius.
- test-typecheck: measured zero, so the package carries no ledger and no
  generator, matching the ten zero-residue plugin packages.
- llms.txt: 68 → 69 published packages.

Claude-Session: https://claude.ai/code/session_6679d191-11f4-465b-b322-0e0409d76793
Co-authored-by: Claude <noreply@anthropic.com>
… last slot erasure (#16130)

- tsconfig `paths` + a vitest alias so every verdict in this package is about
  the source in the checkout, not the last build. That covers the test-only
  `@objectstack/metadata-core` too, which the moved fakes open `update()` with.
- `packages/qa/dogfood` gains the same rule for `@objectstack/organizations`:
  the name became a workspace package with this PR, and that suite's doc
  comment spells a literal dynamic import of it.
- the moved `walled-default-org-self-registrant` fake REFUSES a WHERE
  combinator instead of reading it as a field name — silently wrong is the
  shape that would let a negative assertion read "no rows" as evidence.
- the last `getService(...) as any` in the moved suite goes through the
  harness's own handle.
- engine-double-contract ledger learns the three newly pinned fakes.
- census self-test literal moves with the page it asserts against.

Claude-Session: https://claude.ai/code/session_6679d191-11f4-465b-b322-0e0409d76793
Co-authored-by: Claude <noreply@anthropic.com>
hotlong and others added 2 commits September 6, 2026 14:11
#16130)

The merge driver deferred `content/docs/permissions/system-context.mdx` — it is a
`merge=os-regen` artifact and git ran no text merge on it. This discharges that
deferral from the merged tree, as `.githooks/pre-push` requires.

Anchor lines only: 18 rewritten, all of them `<file>.ts:<line>` citations the
incoming commits moved. Row 62, the section extents and every declared count are
unchanged, and `check:system-context-census` is green on the merged tree.

Claude-Session: https://claude.ai/code/session_6679d191-11f4-465b-b322-0e0409d76793
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/organizations, @objectstack/plugin-security, @objectstack/dogfood, @objectstack/runtime, @objectstack/service-cluster, @objectstack/spec, touching 53 documentable anchor(s). ⚠️ 9 changed file(s) yielded no anchor (packages/plugins/organizations/LICENSE, packages/plugins/organizations/README.md, packages/plugins/organizations/package.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 6c546ab9d0b4842bf363caac2a9ff852277680b5.

2 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 9 changed file(s) yielded no anchor (packages/plugins/organizations/LICENSE, packages/plugins/organizations/README.md, packages/plugins/organizations/package.json, …) — pages documenting those are invisible to this run
  • 2 anchor(s) matched too much of the corpus to be a work list: organization_id (literal, 29 pages), owner_id (literal, 31 pages)
  • 19 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 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 — 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 6c546ab9d0b4842bf363caac2a9ff852277680b5packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 6c546ab9d0b4842bf363caac2a9ff852277680b5

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

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Sep 6, 2026
baozhoutao pushed a commit that referenced this pull request Sep 6, 2026
… residual

The rest of the population #15809 measured, one repair per shape:

- TRACKED TARGETS the surrounding prose already names become file-level
  anchors, and where the prose names the symbol too they become symbol anchors
  the gate now resolves — `packages/objectql/src/plugin.ts#runGovernanceInventory`,
  `packages/plugins/plugin-audit/src/audit-writers.ts#resolveWriteLocale`,
  `scripts/check-system-context-census.mjs#UNENFORCED_TEXT_COUNTS`. A bare
  `engine.ts` was never ambiguous where the prose said which package.
- THIRD-PARTY AND DEPENDENCY SOURCES (`@oclif/core`, `@changesets/pre`) name
  the package, the file and the pinned line as prose. Nothing in this tree can
  resolve them, and a pinned upstream line is provenance, not a pointer.
- ILLUSTRATIONS take the angle-bracket placeholder spelling.

⛔ Again, no number is repaired or repointed: every digit is still on its page.

The residual is 15 citations across six files, and every one of them is a file
another lane holds OPEN — three by PR #16215, one by PR #16202, three by the
in-flight #15776, one by #12511. NOT ONE is a genuinely ambiguous abbreviation.

⚠️ `judgeUntrackedLineAnchors` therefore stays `false`. The fence this work was
dispatched under flips it only when the residual is ZERO and a self-test pins
the flip; flipping it over 15 citations in files this PR may not touch would
make the gate permanently red for the length of somebody else's pull request —
the exact shape the card argued against. `--list-unresolvable` now prints that
residual, so the next author inherits a worklist rather than a count.

Part of #15809.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
hotlong and others added 2 commits September 6, 2026 21:46
#16130)

Second merge round, same deferral: `content/docs/permissions/system-context.mdx`
is a `merge=os-regen` artifact, git ran no text merge on it, and the driver
recorded it in `os-regen-pending`. This discharges that deferral from the merged
tree, as `.githooks/pre-push` requires.

Anchor lines only: 15 rewritten, every one a `file.ts:line` citation the incoming
commits moved. Proven rather than asserted — diffing the page against its
pre-regeneration copy and filtering out anchor-bearing lines leaves nothing, row
62 is byte-identical, and the whole declared-counts table is byte-identical
(106 sites / 20 packages / 45 files, rows 1-62 behaviour, rows 63-66 carry-onward).
baozhoutao pushed a commit that referenced this pull request Sep 6, 2026
…vel anchors

Second round of the scripts/** unresolvable-citation migration PR #16301
started. Two of the six held files freed since that PR landed --
check-docs-section-name.mjs and docs-audit/affected-docs.mjs (PR #16300),
check-type-check-coverage.mjs (PR #16295) -- so their 7 citations migrate
now by the same method: the file named as a file-level anchor, the number
kept beside it as data, no digit repaired or repointed.

- check-docs-section-name.mjs: concept.mdx:426, doc-pages.mdx:257 and
  forms.mdx:183 each resolve to exactly one tracked file under
  content/docs/** (forms.mdx is ambiguous by basename alone -- two files
  share it -- but only content/docs/ui/forms.mdx has a line 183 carrying
  the cited "sections": [/* ... */] shape).
- check-type-check-coverage.mjs: engine.test.ts:2547/2577 resolves to
  packages/services/service-automation/src/engine.test.ts, named by the
  surrounding prose ("service-automation's note").
- docs-audit/affected-docs.mjs: three rest-server.ts citations resolve to
  packages/rest/src/rest-server.ts, the only tracked file of that name.

The corpus residual (scripts/check-scripts-symbol-anchors.mjs
--list-unresolvable) drops from 15 to 8, all 8 still held by open PRs
(#16215, #16202) -- none an ambiguity. judgeUntrackedLineAnchors stays
false per the #15809 fence (flips only at residual zero). Header prose
updated to record both dated readings.

Part of #15809.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant