Skip to content

[RESCUED — evidence complete] test(objectql): collapse the twelve ./registry module-mocks into one factory (#10551) - #10634

Merged
os-elon merged 3 commits into
mainfrom
claude/issue-10551-registry-mock-factory
Aug 21, 2026
Merged

[RESCUED — evidence complete] test(objectql): collapse the twelve ./registry module-mocks into one factory (#10551)#10634
os-elon merged 3 commits into
mainfrom
claude/issue-10551-registry-mock-factory

Conversation

@os-elon

@os-elon os-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #10551

✅ The rescue is now complete — all three structural proofs are in, and the rescued work stands

This PR carries the killed dev's 2 commits unmodified. Nothing was re-implemented, and no
line of the collapse was rewritten. The commits added since are the origin/main merge only.
Measured merge-base anchored against 9185ff0213: 13 files, +288 / −419 — unchanged from
the rescued measurement.

Evidence head: 3a8999ea07. Every number below was produced at that commit.


⭐ Check 0 — the #9002 comment survived, and gained a sibling

Before the collapse the #9002 lesson lived in exactly one of the twelve copies
(engine-middleware-operation-vocabulary.test.ts). It is now in the shared factory,
packages/objectql/src/registry-module-mock.ts, verbatim except for its closing sentence,
which was generalised from one suite to all of them. Quoted as it now stands:

// [#9002] This double used to omit `getAllObjects`, and the suite passed
// anyway: `delete()`'s by-id branch reads it twice (`planCascadeAtomicity`,
// then `cascadeDeleteRelations`) and BOTH reads sat behind a `catch` that
// answered "no relations". The swallow absorbed the `TypeError` this
// omission raises just as silently as it would absorb a real read failure,
// so an incomplete double read as a registry with nothing in it. With the
// swallows gone the omission is a hard failure, which is the point — the
// double now has to model the method the engine actually calls. Empty is
// the right body here: a suite that registers no relations is telling the
// truth when it says "no object references the deleted one", where a double
// that cannot answer at all leaves the engine to invent it.

The only edit is the last sentence. Before, it read "Empty is the right body here: this suite
pins the middleware operation VOCABULARY and registers no relations, so 'no object references
the deleted one' is the truthful answer rather than an invented one."
— suite-specific, and
untrue of the other eleven inheritors. The generalisation is correct for a shared factory.

The dev also carried in the #9154 lesson, which at merge base was spread across ten files,
and added a note recording why both now live in one place:

// ⚠️ Both lessons were written into ONE of the twelve hand-copies each, and
// neither could reach the other eleven. That is why they live here (#10551).
// A suite whose truthful answer is NOT "no objects" overrides this member —
// it does not delete it.

The cleanup did not delete the reason for the cleanup.


⭐ Proof 1 — the share proof: one deleted line reddens ten files

Deleted getAllObjects: vi.fn(() => []) (line 158) from the shared factory's default member
set, ran the whole package, then restored.

 Test Files  10 failed | 215 passed (225)
      Tests  86 failed | 3897 passed (3983)

Ten of the twelve call sites reddened from a single deleted line — a half-done refactor that
had left copies hand-rolled could not do this:

reddened file
engine-autonumber-default-format.test.ts
engine-autonumber-defer.test.ts
engine-autonumber-resync.test.ts
engine-autonumber-seed-outage.test.ts
engine-autonumber-seed-scan.test.ts
engine-autonumber-seed-suffix.test.ts
engine-filter-tokens.test.ts
engine-middleware-operation-vocabulary.test.ts
engine-multivalue-normalize.test.ts
engine-validation-locale.test.ts
engine-count-read-filter.test.ts
engine.test.ts

The two that stayed green are exactly the two the model predicts, and each confirms a
separate claim.
engine-count-read-filter.test.ts is the outlier that never declared
getAllObjects at all — its staying green is an independent re-confirmation of the filer's
inertness measurement. engine.test.ts overrides getAllObjects with a real in-memory
body, so the default's absence cannot reach it — which proves the override path works and that
an overriding suite still inherits the other eleven members.

The failure text, and note where it lands:

TypeError: this._registry.getAllObjects is not a function
    at ObjectQL.buildSummaryIndex (packages/objectql/src/engine.ts:7327:53)
    at ObjectQL.ensureSummaryIndexes (packages/objectql/src/engine.ts:7431:22)
    at ObjectQL.getSummaryDescriptors (packages/objectql/src/engine.ts:7449:10)
    at ObjectQL.recomputeSummaries (packages/objectql/src/engine.ts:7566:30)

That is the #9154 call site — the roll-up summary index read — failing hard rather than
silently answering "no objects". The lesson comment's own claim ("With the optional call gone
the omission is a hard TypeError") is therefore not just documented here but measured.

Restore proved on disk, not by an editor's exit code:

git diff --exit-code   →  exit 0
git status --porcelain →  (empty)
grep -n 'getAllObjects: vi.fn' packages/objectql/src/registry-module-mock.ts
  →  158:    getAllObjects: vi.fn(() => [])
sha256 of the file  ==  sha256 of the pre-ablation snapshot
  72ed3afaf5dd6e072569a9573248a6f76cbed7ab16e3ce5375cc2da5ce939892

No rebuild was needed for this ablation and none is claimed: the twelve suites reach the factory
through the relative specifier ./registry-module-mock.js inside the same package, which
vitest transforms from source. Nothing resolves through this package's exports to dist/, so
there is no stale-artifact path for the measurement to hide in — independently corroborated by
check:test-source-alias, which registers the package's dist-resolving imports and did not
grow one for this module.


Proof 2 — the superset proof

Member sets read by AST (TypeScript compiler API) from each pre-collapse file's
const instance object literal, at merge base.

Before — eleven files, 12 members each:

getObject, resolveObject, getAllObjects, registerObject, getObjectOwner,
registerNamespace, registerKind, registerItem, registerApp, installPackage,
reset, metadata

Before — the outlier engine-count-read-filter.test.ts, 11 members:

getObject, resolveObject, registerObject, getObjectOwner,
registerNamespace, registerKind, registerItem, registerApp, installPackage,
reset, metadata

getAllObjects absent, exactly as the card states.

After — the shared factory's default set, 12 members plus an overrides spread:

getObject, resolveObject, getAllObjects, registerObject, getObjectOwner,
registerNamespace, registerKind, registerItem, registerApp, installPackage,
reset, metadata, ...instanceOverrides

The after set equals the union of all twelve before-sets, and is therefore a superset of
every one of them individually — a strict superset of the outlier's eleven. RegistryDoubleInstance
additionally carries an index signature, so a suite may add a member without a type edit.

Two things checked beyond the member names, because a matching name set can still hide a
behaviour change:

  1. The module envelope. Eleven copies declared identical computeFQN / parseFQN /
    RESERVED_NAMESPACES, and those are exactly the factory's defaults. engine.test.ts
    declared a namespace-aware pair, and it passes them through the factory's
    computeFQN / parseFQN options — preserved, not defaulted away.
  2. Member bodies. engine.test.ts is the only suite with stateful bodies. The four members
    it now inherits rather than declares (registerNamespace, registerKind, registerItem,
    registerApp) were bare vi.fn() in the pre-collapse file, so inheritance is
    behaviour-identical. Verified by reading the merge-base source, not assumed.

Also confirmed: twelve is the complete population. A merge-base scan for
vi.mock('./registry' under packages/objectql/src/ returns exactly 12 files, and they are
exactly the 12 the collapse touched. No thirteenth copy was left hand-rolled.


Proof 3 — suite numbers and the gate union

Suite, whole package, at 3a8999ea07:

 Test Files  225 passed (225)
      Tests  3983 passed (3983)
   Duration  132.59s

(The filer's pre-change baseline was 224 / 3956; the growth is landings merged from main,
not this branch — this branch adds no test case, it removes duplicated fixture code.)

Typecheck: pnpm --filter @objectstack/objectql typecheck → exit 0, with the script name
echoed in the output so this is not a zero-match silent pass. Note this also exercises the new
module: registry-module-mock.ts is deliberately not named *.test.ts, so unlike the twelve
copies it replaces it is inside the program tsc --noEmit reads.

Gate union — derived by node scripts/pm/dispatch-gates.mjs with no path arguments, after
the final commit, at 3a8999ea07. Each row quotes the gate's own verdict line.

gate verdict line
check:durability-log-level ✓ durability-degradation log levels: 30 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller
check:slot-lookup ✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new
check:test-source-alias check-test-source-alias OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/
check:type-source-resolution check-type-source-resolution OK — 76 packages with a tsconfig.json scanned
check-engine-split-ratio.mjs exit 0 — ratio: 97.5% (a reported metric, no threshold)
check-affected-docs.mjs exit 0
check:query-options-erasure ✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new · test surface: 240 site(s) in 47 file(s) — at the ceiling
check:type-check-coverage check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger
check:type-check-debt check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 222.5s, 1912 raw tsc error(s) total, none above its recorded number
check:engine-double-contract check-engine-double-contract: OK — 371 pinned, 133 in the DEBT ledger, 2 exempt
check:where-matcher ✓ where-matcher conformance holds: 271 matcher(s) discovered, 271 answer the combinator battery correctly or refuse it loudly
check:nul-bytes check-nul-bytes: OK (scanned 6232 text file(s) ... no raw ASCII control bytes)

check:type-check-debt requires a built workspace closure, so the full farm build was run first
(turbo run build over ./packages/* and ./packages/*/*): 70 successful, 70 total, 5m17s.
It was run with nothing else concurrent and a 4 GB heap cap, given what killed the previous
three dispatches on this box.

⚠️ No ratchet was raised. @objectstack/objectql's ledger entries did not move in either
direction, and no ledger file appears in this diff.


Changeset: deliberately none, plus skip-changeset

Reasoning, stated rather than omitted. This is test-only and publishes nothing:

  • The twelve changed files are all *.test.ts.
  • The one new non-test file, packages/objectql/src/registry-module-mock.ts, is not reachable
    from either tsup entry (src/index.ts, src/core.ts). Verified two ways: no import of it
    exists anywhere outside the twelve test files, and a grep of the freshly built
    packages/objectql/dist/ for createRegistryModuleMock and registry-module-mock returns
    zero occurrences.
  • No runtime source changed, so no released behaviour changed. A version bump would assert a
    release that does not exist.

The repo's mechanism for that declaration is the skip-changeset label — the author's explicit
opt-out read by changeset-check in pr-automation.yml — so the label is applied here as part
of this PR rather than left to CI.


What I deliberately did NOT do

  • Did not re-implement or "improve" the collapse. Every measurement above says the rescued
    work is correct; there was nothing to rewrite, and rewriting it unprompted was out of scope.
  • Did not add a probe to test vi.mock hoisting directly. This lane was bitten once by a
    probe that pre-empted hoisting and turned two unrelated tests red. No probe is needed: the
    async-factory form is confirmed by the whole suite running green through it, and the ablation
    proves the shared module is genuinely what all ten reddened suites receive at runtime. A
    synthetic probe would only add artefact risk to an already-answered question.
  • Did not touch @objectstack/plugin-auth's TEST_DEBT surplus. check:type-check-debt
    reports its ledger records 109 where tsc now measures 97, and offers --lower. That gap is
    pre-existing on main, belongs to another author's package, and is already tracked upstream by
    the standing issue the gate's own message names. Lowering it here would be unrelated scope.

Generated by Claude Code

claude added 2 commits August 21, 2026 04:23
…e factory

`vi.mock('./registry', …)` was hand-copied into twelve test files in
`packages/objectql/src`, and the copies had drifted: eleven declared twelve
members, `engine-count-read-filter.test.ts` declared eleven and omitted
`getAllObjects` — the #9002 shape, inert only because no path its suite drives
reaches one of the thirteen `getAllObjects` call sites in this package.

The deciding evidence for collapsing rather than adding the missing line is the
two lesson comments themselves: the #9002 explanation lived in exactly one copy
and the #9154 explanation in nine others, and neither could reach the rest
because there was no shared factory to write them in. Both now live in
`registry-module-mock.ts`, which every call site inherits.

`engine.test.ts` keeps its stateful in-memory registry as per-member overrides
over the shared member set, so it too fails when the shared factory loses a
member. The `async` factory form is what makes importing the shared module legal
under `vi.mock` hoisting.

Test infrastructure only — no production source is touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 22 documentable anchor(s).

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

  • content/docs/concepts/metadata-lifecycle.mdx (via SchemaRegistry (symbol))
  • content/docs/kernel/contracts/metadata-service.mdx (via getObject (symbol))
  • content/docs/kernel/services-checklist.mdx (via SchemaRegistry (symbol))
  • content/docs/plugins/adding-a-metadata-type.mdx (via SchemaRegistry (symbol))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via installPackage (symbol))
  • content/docs/releases/v17.mdx (via SchemaRegistry (symbol), getLegalNextStates (sdk), meta.getLegalNextStates (sdk))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 14 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 9185ff0213c1590bf3808d5955cddd2becf924eapackageMentionDocs.

Which tree this was computed on

This run read content/docs from 884572b6430af1884160589ab74e99545b46333e — the merge of head 3a8999ea07a54649e3143aaa97dd438b965c4c80 into base 9185ff0213c1590bf3808d5955cddd2becf924ea, 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 884572b6430af1884160589ab74e99545b46333e && git checkout 884572b6430af1884160589ab74e99545b46333e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9185ff0213c1590bf3808d5955cddd2becf924ea 3a8999ea07a54649e3143aaa97dd438b965c4c80 && git checkout -B drift-repro 9185ff0213c1590bf3808d5955cddd2becf924ea && git merge --no-ff 3a8999ea07a54649e3143aaa97dd438b965c4c80

node scripts/docs-audit/affected-docs.mjs --json 9185ff0213c1590bf3808d5955cddd2becf924ea

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

@os-elon os-elon changed the title [RESCUED — evidence incomplete] test(objectql): collapse the twelve ./registry module-mocks into one factory (#10551) [RESCUED — evidence complete] test(objectql): collapse the twelve ./registry module-mocks into one factory (#10551) Aug 21, 2026
@os-elon os-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 10:25
@os-elon
os-elon enabled auto-merge August 21, 2026 10:25
@os-elon
os-elon added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 4b84834 Aug 21, 2026
37 of 38 checks passed
@os-elon
os-elon deleted the claude/issue-10551-registry-mock-factory branch August 21, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants