You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] engine doubles have no assertEngineFindOnePredicate — a fake answers a predicate-less findOne that the real engine REFUSES, and #11767 is what that costs #11957
@objectstack/objectql publishes two shared dispatch asserts that in-repo engine doubles must adopt — assertEngineUpdateDispatch and assertEngineDeleteDispatch — and pnpm check:engine-double-contract ratchets their adoption. There is no equivalent for the read side, specifically for the findOne predicate rule.
The real engine (packages/objectql/src/engine.ts → requireFindOnePredicate, #4419) throws on a findOne whose where is absent/empty and which carries no orderBy:
findOne('sys_user') selects no particular record: 'where' is absent or empty and the query
carries no 'orderBy'. findOne applies limit: 1, so this would return an ARBITRARY row ...
Every in-memory double in the repo instead treats an absent filter as "match everything" and answers happily — null on an empty table, an arbitrary row otherwise. So a production call site that violates #4419 reads as working under every unit suite and only fails on a real engine.
What it cost, measured
objectstack#11767 (audience posture, #11739). AuthManager.isBootstrapCreation probed the bootstrap population with adapter.findOne({ model: 'user', where: [] }) inside a try/catch. On a real ObjectQL engine that call throws; the catch { return false; } turned the refusal into "users exist", so the declared first-run bypass never fired and the invite_only default refused the operator's own dev-admin seed. Two required gates went red across every shard (Dogfood Regression Gate 1–3/3, Test Core 1/6 + 2/6) — while a 641-line posture x creation-method matrix over the in-memory double stayed green, including a case literally named "bootstrap: the very first signup is admitted".
Instrumented at the real seam during a dogfood boot:
OSPROBE adapter=true findOne=function
OSPROBE adapter.findOne THREW: findOne('sys_user') selects no particular record ... (#4419)
at _ObjectQL.requireFindOnePredicate (packages/objectql/src/engine.ts:8408)
OSPROBE engine sys_user rows=[] <- zero users; the verdict was false
The call site is fixed in #11767 and pinned by a real-engine test. The class is not: nothing stops the next double-backed suite from certifying the next predicate-less findOne.
Suggested shape
Publish assertEngineFindOnePredicate(query) from @objectstack/objectql beside the two existing dispatch asserts — the producer's own predicate, not a hand-copied re-derivation (that is the hole check:engine-double-contract already names for the delete side) — and extend the ratchet to it. Note the sizing: the adoption wave touches every findOne double in the ledger, so this is its own card, not a rider.
Concentrated triage batch: finding → pm:queue + domain:engine, Task, M — the read-side gap in the engine-double contract family: publish assertEngineFindOnePredicate from @objectstack/objectql mirroring requireFindOnePredicate's #4419 refusal, adopt it in the in-repo doubles, and extend check:engine-double-contract's ratchet to the read slice (gate half coordinates with the devx-owned gate file — declare the cross-lane file surface at claim). The cost is measured, not hypothetical: #11767 is what a predicate-less findOne answered by a happy fake already cost.
Claim: PM loop (maintainer direct-dispatch channel)
Session: session_71836b57-5db6-459d-ae4d-c6d9d7dd2cc6
Branch: claude/issue-11957-engine-double-findone-predicate
Worktree: objectstack-issue-11957
Domain: domain:engine
File surface: the engine-double test-support/ratchet surface and the doubles it names (stop on breach; explain in the report — the adoption wave's true size is the first thing to measure)
Container & model: M, mode:subagent, model: opus
Clause-②: no
Serial constraints cleared: different lane and different packages from the in-flight #11770 (plugin-auth) — no file-surface intersection. This card is the reason a 641-line green matrix said nothing about the bootstrap defect that #11739 shipped with, so it is graded as a real guard gap, not polish.
{
"issue": 11957,
"status": "done",
"branch": "claude/issue-11957-engine-double-findone-predicate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12072",
"premise_still_valid": true,
"summary": "INVENTORY FIRST, as the card demanded. Measured by temporarily adding a `findOne` slice to `SLICES` in scripts/check-engine-double-contract.mjs and running the gate: 232 files / 275 findOne doubles would newly fail. 169 of those files are already in the RETAINED pinned ledger for a write verb; 23 are DEBT-baseline-only; 40 are in neither ledger. That is far past a reviewable diff, and 63 of the 232 cannot be closed by a dev seat at all — the gate's own failure text declares a baseline entry MAINTAINER-ONLY ('do not take this path to get CI green'). So I landed OPTION B, the bounded slice, and say so explicitly: the guard plus the adoption for `packages/objectql` (the producer's own package, 23 files / 25 doubles, all already in the pinned ledger), and I did NOT add the findOne slice to the gate. Shipped: `assertEngineFindOnePredicate` / `resolveEngineFindOnePredicate` / `engineFindOnePredicateRefusalMessage` / `ENGINE_FINDONE_PREDICATE_CASES` from @objectstack/metadata-core (re-exported by @objectstack/objectql at the twins' spelling), mirroring `requireFindOnePredicate` byte-for-byte including the object name the message quotes twice — hence the `(object, query)` signature, unlike the two write-side asserts. It folds the `filter` alias and reads `where: []` as NO predicate (the #11767 shape a truthiness copy accepts) and `where: {}` as none either; two one-sided residuals a double structurally cannot see (a `search` over an object with no searchable field, and a malformed non-empty `where` array) are named in the module header rather than hidden. The 209-file / 250-double remainder is enumerated file-by-file, broken down per package by ledger status, in the follow-up card #12068, together with the maintainer decision the 63 non-mechanical ones need. NOTHING is silently truncated. Note one prompt-vs-file point: os-dev.md prefers `Part of #n` when a merge should not close a card; the dispatch prompt explicitly specified `Fixes #11957` and that reads consistent with 'file a follow-up card naming the exact remainder', so the PR says Fixes and the remainder lives in #12068 — flagged here rather than chosen silently.",
"tests": "ALL RUNS DECLARED UNLOCKED: scripts/pm/os-verify-lock.sh refused the shared lock on this macOS host (no usable flock, declared Linux-only); every heavy command below ran through the wrapper, which printed 'VERDICT command-exit 0 · UNLOCKED (declared)'. Verdicts are quoted from each gate's own line, never a bare $?. (1) Gate union re-run ON THE FINAL COMMIT cc3697f8e, working tree clean: check:engine-double-contract exit 0 ('OK — 408 pinned, 133 in the DEBT ledger, 2 exempt'), check:query-options-erasure exit 0 ('test surface: 240 site(s) in 47 file(s) — at the ceiling'), check:slot-lookup 0, check:nul-bytes 0 ('scanned 6696 text file(s) ... no raw ASCII control bytes'), check:type-check-coverage 0, check:durability-log-level 0, check:where-matcher 0. Green earlier on the same tree: check:cross-package-test-inputs, check:test-source-alias, check:published-files, check:adr-0087-registration, check:type-source-resolution. Gate family derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (it confirmed the repo assertion holds), not hand-picked. (2) Tests: `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 <23 adopted files> src/engine-findone-predicate.test.ts` → 'Test Files 24 passed (24) / Tests 306 passed (306)'. NO test went red under the assertion, so no test in this subset was relying on the permissive double and no product defect surfaced here — the looseness in objectql's own doubles is dormant, which is a finding about this subset only, not a clean bill for the 209. (3) Typecheck: `pnpm --filter @objectstack/metadata-core --filter @objectstack/objectql typecheck` → both 'Done', exit 0. (4) ABLATION, both legs rebuilt and both proved on disk. Mutation: `return where.length > 0` → `return ['OSABLATE'].length > 0` (the naive truthy read of `where: []`). Disk proof anchored at the text being replaced, not a bare --stat: original-text hits 1→0 and marker hits 0→1 on the mutate leg, reversed on the restore leg. dist proof: `node scripts/ablation-dist-preflight.mjs @objectstack/metadata-core 'OSABLATE'` → '✓ marker present in 2 built files'; restore leg with --absent → '✓ marker absent from all 18 built files'. Results: mutated 3 failed | 21 passed (exactly the where:[] case, the refusal-message assertion and the where:[] unit — the direction predicted BEFORE the run); restored 24 passed. ⚠️ FIRST ATTEMPT WAS VOID and is reported rather than quietly redone: it used a `/* comment */` marker, and the preflight correctly reported the marker landed ONLY in sourcemaps and voided the run; it was redone with a string literal. Both ablation scripts carried `trap … EXIT INT TERM` restores. (5) DECLARED LINT NARROWING, with all three pieces of evidence: ① population handed to ESLint as the diff's own paths and selected by ESLint's OWN config resolution (the changeset .md came back 'File ignored because no matching configuration was supplied' — the config deciding, not me); ② `--format json` reports 25 entries, 24 TypeScript files actually linted, 0 errors 0 warnings (the lone warning is that ignored .md); ③ type-aware linting is NOT enabled anywhere in eslint.config.mjs (no parserOptions.project, no typed @typescript-eslint rules — the config states this at its line 328), so this diff cannot move the verdict on any untouched file. (6) check:type-check-debt REFUSED TO RUN (exit 1) and that is an ENVIRONMENT verdict, not a finding about this diff: '--re-measure cannot run: 42 workspace dependenc(ies) ... have no built type entry point on disk'. It needs the full built closure (`turbo run build --filter=./packages/*`), which CI does; its sibling check:type-check-coverage ran and is green. Declared, not skipped. (7) check:query-options-erasure was RED at 240→242 and was fixed at the source, never by raising the ceiling: isolated to my new conformance test by holding that one file aside and re-running (240, 'at the ceiling'), then remedied with the sanctioned spelling `as unknown as EngineQueryOptions` plus a typed `EngineFindOneQueryInput` on the widened double signatures. Back to 240.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #12068: extend check:engine-double-contract with the findOne slice and adopt assertEngineFindOnePredicate across the remaining 209 files / 250 doubles — every file enumerated, per-package ledger breakdown, and the maintainer decision the 63 non-mechanical ones need (23 DEBT-ledgered + 40 unledgered)"
]
}
The card's hardest requirement was "measure the wave before committing to it", and that is what decided the shape. Measured by temporarily adding a findOne slice to the gate's own SLICES: 232 files / 275 doubles would newly fail; 169 already pinned for a write verb, 23 DEBT-baseline-only, 40 in neither ledger. Past a reviewable diff by a wide margin — and 63 of them cannot be closed by a dev seat at all, because the gate's own failure text declares a baseline entry maintainer-only. Landing the bounded slice was the right call, and the report says so explicitly rather than presenting a partial sweep as a complete one.
The assertion mirrors the engine rather than approximating it — including reading where: [] as NO predicate, which is exactly the #11767 shape a truthiness copy accepts and the reason this class of defect shipped. Two residuals a double structurally cannot see are named in the module header instead of hidden.
No test went red under the assertion in the adopted subset — correctly reported as "the looseness in objectql's own doubles is dormant", scoped to this subset, and explicitly NOT a clean bill for the other 209.
Two process points handled the way I want them handled: the first ablation attempt was void (a /* comment */ marker landed only in sourcemaps; the preflight caught it) and was reported rather than quietly redone; and check:query-options-erasure went red at 240→242 and was fixed at the source, never by raising the ceiling — the single most tempting wrong move in a ratchet repo.
Fixes #11957 vs Part of: flagged rather than chosen silently. Fixes is right — this card asked for the guard plus a bounded adoption, and the remainder is its own card.
No packages/spec in the diff (checked: 0 files), so no contract-review chain applies.
Landing: enqueue once the remaining checks report green.
{
"issue": 11957,
"status": "done",
"branch": "claude/issue-11957-engine-double-findone-predicate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12072",
"premise_still_valid": true,
"summary": "RESUME ROUND — the CI red is fixed and both previously-red contexts are green on the new head 885a32bc3. ROOT CAUSE, one error, exactly as the PM quoted: my new conformance test called `engine.registry.registerObject({ … })` with ONE argument, but the signature is `registerObject(schema, packageId, namespace?, ownership?, priority?)` — `packageId` is required, so it is a TS2554 'Expected 2-5 arguments, but got 1'. It was invisible locally and to the package's own `typecheck` because `packages/objectql/tsconfig.json` excludes `**/*.test.ts`; the ONLY reader of that layer is the shrink-only TEST_DEBT re-measure, which is why the sole symptom was 354 -> 355. FIX: pass 'test-package' (plus a comment saying why the one-argument spelling other doubles in this package still use is invisible), commit 885a32bc3. NOTHING ELSE TOUCHED: `scripts/check-type-check-coverage.mjs` is untouched, no TEST_DEBT entry was raised, and I did NOT run `--lower` for the unrelated `@objectstack/plugin-approvals: -1` note (it is still printed as informational, and it is not mine). THE SECOND RED WAS THE SAME ERROR, NOT A SEPARATE ONE — asked and answered from the job log: `TypeScript Type Check` is an AGGREGATOR over four lanes, and its whole failure text was 'type-check lane `typecheck-debt` concluded `failure` -- expected `success`' with typecheck-source-gates / typecheck-workspace / typecheck-consumers all `success`. One error, two red contexts.",
"tests": "REPRODUCED THE CI PATH, not a proxy for it. `check:type-check-debt` had refused locally last round because the re-measure needs the full built closure; this round I built it exactly as lint.yml does — `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*'` → '70 successful, 70 total', 3m23s — and then re-ran the gate for real. (1) NARROW MEASUREMENT that located the error: rebuilt `remeasureProject`'s generated project for objectql (drop the test globs from `exclude`, keep its own `include`, default `typeRoots` from the package upward) and ran `tsc --noEmit -p <that config>`. BEFORE: 356 raw diagnostics, 1 of them TS6059 (the one the gate drops), i.e. 355 — matching CI's 355 exactly. Grepping the diagnostics by file attributed exactly ONE to my diff: 'packages/objectql/src/engine-findone-predicate.test.ts(65,19): error TS2554: Expected 2-5 arguments, but got 1.' AFTER the fix: 355 raw, 1 TS6059 → 354, equal to the ledger, and ZERO diagnostics attributed to that file. (2) THE REAL GATE, locally: `pnpm check:type-check-debt --re-measure` exit 0, its own verdict line 'check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 150.0s, 1898 raw tsc error(s) total, none above its recorded number.' `git status --porcelain` after it showed ONLY the one test file — no ledger write. (3) Behaviour unchanged: `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/engine-findone-predicate.test.ts` → 'Test Files 1 passed (1) / Tests 24 passed (24)'. `pnpm --filter @objectstack/objectql typecheck` exit 0. ESLint over the changed file: 1 file, 0 errors, 0 warnings. (4) Ratchet family re-run ON THE FINAL COMMIT 885a32bc3, tree clean: check:engine-double-contract 0, check:query-options-erasure 0, check:nul-bytes 0, check:type-check-coverage 0. (5) CI, BEFORE and AFTER, both contexts — this is the verification the PM asked for. BEFORE (head cc3697f8e): 'Type Check · debt ledger' = FAILURE (job 97723042289, '• @objectstack/objectql: TEST_DEBT records 354 raw tsc error(s), `tsc --noEmit` now reports 355 (+1)'); 'TypeScript Type Check' = FAILURE (job 97725612681, 'check-typecheck-lanes: FAILED (1 problem(s) above)' — the aggregator). AFTER (head 885a32bc3): 'Type Check · debt ledger' = SUCCESS (https://github.com/objectstack-ai/objectstack/actions/runs/32833208711/job/97756275305), its own verdict line from the CI log: 'check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 238.4s, 1897 raw tsc error(s) total, none above its recorded number.'; 'TypeScript Type Check' = SUCCESS (https://github.com/objectstack-ai/objectstack/actions/runs/32833208711/job/97758739847). The whole head is now green: 29 success, 2 skipped, 0 failing, 0 pending — including all six required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL)), each `success`. PR 12072 is still DRAFT and unmerged; I flipped nothing and touched no labels. DECLARED NARROWING, unchanged from last round: `scripts/pm/os-verify-lock.sh` cannot take the shared verify lock on this macOS host (no usable `flock`, declared Linux-only), so the local runs above were unserialized — declared, not silent. Prior-round evidence (the guard's ablation, the 24-file/306-test adoption run, the lint narrowing with its three pieces of evidence) stands unchanged and is in the PR body.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #12068 (last round, still open): extend check:engine-double-contract with the findOne slice and adopt assertEngineFindOnePredicate across the remaining 209 files / 250 doubles — every file enumerated, per-package ledger breakdown, and the maintainer decision the 63 non-mechanical ones need",
"NOT filed, reported only: `@objectstack/plugin-approvals` TEST_DEBT records 348 while tsc now reports 347 (-1). The gate prints it as an informational surplus and names `--lower` as the remedy. It is not mine and not this card's, so per the dispatch I left it alone; it is already tracked upstream as objectstack#6376, which the gate's own message cites."
]
}
ACCEPT (round 2 — the CI red is fixed) on PR #12072, head 885a32bc3. Verified against check-runs, not the report: 0 failing, 0 pending, and all six required contexts read success (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance). Independently confirmed the ledger script is absent from the diff (0 files matching check-type-check-coverage), so the forbidden shortcut was not taken.
The defect was one line and the diagnosis is worth keeping: the new conformance test called registry.registerObject({...}) with a single argument against a (schema, packageId, …) signature — TS2554. It was invisible to the package's own typecheck because packages/objectql/tsconfig.json excludes **/*.test.ts, so the only reader of that layer is the shrink-only TEST_DEBT re-measure — which is exactly why the sole symptom was 354 → 355 and why nothing local caught it. The dev reproduced the CI path rather than a proxy for it (built the full closure the way lint.yml does, then rebuilt the gate's own generated project and attributed the single diagnostic to the offending file), and the before/after counts land on the ledger number exactly.
It also answered the question this seat asked rather than guessing: the second red was the same error, not a separate one — TypeScript Type Check is an aggregator over four lanes and its entire failure text was "type-check lane typecheck-debt concluded failure", with the other three lanes green.
Two restraints held under pressure, which is the part that matters in a ratchet repo: the TEST_DEBT entry was not raised (the gate declares that maintainer-only, "do not take this path to get CI green"), and the unrelated plugin-approvals: -1 surplus printed in the same output was left alone and reported rather than opportunistically closed.
Round-1 verdict stands unchanged (inventory-first sizing, the bounded objectql slice, the enumerated 209-file remainder in #12068, the void-then-redone ablation, and the check:query-options-erasure regression fixed at source rather than by raising the ceiling).
Activity
claude commentedon Aug 25, 2026
Concentrated triage batch:
finding→pm:queue+domain:engine, Task, M — the read-side gap in the engine-double contract family: publishassertEngineFindOnePredicatefrom@objectstack/objectqlmirroringrequireFindOnePredicate's #4419 refusal, adopt it in the in-repo doubles, and extendcheck:engine-double-contract's ratchet to the read slice (gate half coordinates with the devx-owned gate file — declare the cross-lane file surface at claim). The cost is measured, not hypothetical: #11767 is what a predicate-less findOne answered by a happy fake already cost.Generated by Claude Code
os-zhuang commentedon Aug 25, 2026
Claim: PM loop (maintainer direct-dispatch channel)
Session:
session_71836b57-5db6-459d-ae4d-c6d9d7dd2cc6Branch:
claude/issue-11957-engine-double-findone-predicateWorktree:
objectstack-issue-11957Domain:
domain:engineFile surface: the engine-double test-support/ratchet surface and the doubles it names (stop on breach; explain in the report — the adoption wave's true size is the first thing to measure)
Container & model: M,
mode:subagent,model: opusClause-②: no
Serial constraints cleared: different lane and different packages from the in-flight #11770 (
plugin-auth) — no file-surface intersection. This card is the reason a 641-line green matrix said nothing about the bootstrap defect that #11739 shipped with, so it is graded as a real guard gap, not polish.os-zhuang commentedon Aug 25, 2026
{ "issue": 11957, "status": "done", "branch": "claude/issue-11957-engine-double-findone-predicate", "pr": "https://github.com/objectstack-ai/objectstack/pull/12072", "premise_still_valid": true, "summary": "INVENTORY FIRST, as the card demanded. Measured by temporarily adding a `findOne` slice to `SLICES` in scripts/check-engine-double-contract.mjs and running the gate: 232 files / 275 findOne doubles would newly fail. 169 of those files are already in the RETAINED pinned ledger for a write verb; 23 are DEBT-baseline-only; 40 are in neither ledger. That is far past a reviewable diff, and 63 of the 232 cannot be closed by a dev seat at all — the gate's own failure text declares a baseline entry MAINTAINER-ONLY ('do not take this path to get CI green'). So I landed OPTION B, the bounded slice, and say so explicitly: the guard plus the adoption for `packages/objectql` (the producer's own package, 23 files / 25 doubles, all already in the pinned ledger), and I did NOT add the findOne slice to the gate. Shipped: `assertEngineFindOnePredicate` / `resolveEngineFindOnePredicate` / `engineFindOnePredicateRefusalMessage` / `ENGINE_FINDONE_PREDICATE_CASES` from @objectstack/metadata-core (re-exported by @objectstack/objectql at the twins' spelling), mirroring `requireFindOnePredicate` byte-for-byte including the object name the message quotes twice — hence the `(object, query)` signature, unlike the two write-side asserts. It folds the `filter` alias and reads `where: []` as NO predicate (the #11767 shape a truthiness copy accepts) and `where: {}` as none either; two one-sided residuals a double structurally cannot see (a `search` over an object with no searchable field, and a malformed non-empty `where` array) are named in the module header rather than hidden. The 209-file / 250-double remainder is enumerated file-by-file, broken down per package by ledger status, in the follow-up card #12068, together with the maintainer decision the 63 non-mechanical ones need. NOTHING is silently truncated. Note one prompt-vs-file point: os-dev.md prefers `Part of #n` when a merge should not close a card; the dispatch prompt explicitly specified `Fixes #11957` and that reads consistent with 'file a follow-up card naming the exact remainder', so the PR says Fixes and the remainder lives in #12068 — flagged here rather than chosen silently.", "tests": "ALL RUNS DECLARED UNLOCKED: scripts/pm/os-verify-lock.sh refused the shared lock on this macOS host (no usable flock, declared Linux-only); every heavy command below ran through the wrapper, which printed 'VERDICT command-exit 0 · UNLOCKED (declared)'. Verdicts are quoted from each gate's own line, never a bare $?. (1) Gate union re-run ON THE FINAL COMMIT cc3697f8e, working tree clean: check:engine-double-contract exit 0 ('OK — 408 pinned, 133 in the DEBT ledger, 2 exempt'), check:query-options-erasure exit 0 ('test surface: 240 site(s) in 47 file(s) — at the ceiling'), check:slot-lookup 0, check:nul-bytes 0 ('scanned 6696 text file(s) ... no raw ASCII control bytes'), check:type-check-coverage 0, check:durability-log-level 0, check:where-matcher 0. Green earlier on the same tree: check:cross-package-test-inputs, check:test-source-alias, check:published-files, check:adr-0087-registration, check:type-source-resolution. Gate family derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (it confirmed the repo assertion holds), not hand-picked. (2) Tests: `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 <23 adopted files> src/engine-findone-predicate.test.ts` → 'Test Files 24 passed (24) / Tests 306 passed (306)'. NO test went red under the assertion, so no test in this subset was relying on the permissive double and no product defect surfaced here — the looseness in objectql's own doubles is dormant, which is a finding about this subset only, not a clean bill for the 209. (3) Typecheck: `pnpm --filter @objectstack/metadata-core --filter @objectstack/objectql typecheck` → both 'Done', exit 0. (4) ABLATION, both legs rebuilt and both proved on disk. Mutation: `return where.length > 0` → `return ['OSABLATE'].length > 0` (the naive truthy read of `where: []`). Disk proof anchored at the text being replaced, not a bare --stat: original-text hits 1→0 and marker hits 0→1 on the mutate leg, reversed on the restore leg. dist proof: `node scripts/ablation-dist-preflight.mjs @objectstack/metadata-core 'OSABLATE'` → '✓ marker present in 2 built files'; restore leg with --absent → '✓ marker absent from all 18 built files'. Results: mutated 3 failed | 21 passed (exactly the where:[] case, the refusal-message assertion and the where:[] unit — the direction predicted BEFORE the run); restored 24 passed. ⚠️ FIRST ATTEMPT WAS VOID and is reported rather than quietly redone: it used a `/* comment */` marker, and the preflight correctly reported the marker landed ONLY in sourcemaps and voided the run; it was redone with a string literal. Both ablation scripts carried `trap … EXIT INT TERM` restores. (5) DECLARED LINT NARROWING, with all three pieces of evidence: ① population handed to ESLint as the diff's own paths and selected by ESLint's OWN config resolution (the changeset .md came back 'File ignored because no matching configuration was supplied' — the config deciding, not me); ② `--format json` reports 25 entries, 24 TypeScript files actually linted, 0 errors 0 warnings (the lone warning is that ignored .md); ③ type-aware linting is NOT enabled anywhere in eslint.config.mjs (no parserOptions.project, no typed @typescript-eslint rules — the config states this at its line 328), so this diff cannot move the verdict on any untouched file. (6) check:type-check-debt REFUSED TO RUN (exit 1) and that is an ENVIRONMENT verdict, not a finding about this diff: '--re-measure cannot run: 42 workspace dependenc(ies) ... have no built type entry point on disk'. It needs the full built closure (`turbo run build --filter=./packages/*`), which CI does; its sibling check:type-check-coverage ran and is green. Declared, not skipped. (7) check:query-options-erasure was RED at 240→242 and was fixed at the source, never by raising the ceiling: isolated to my new conformance test by holding that one file aside and re-running (240, 'at the ceiling'), then remedied with the sanctioned spelling `as unknown as EngineQueryOptions` plus a typed `EngineFindOneQueryInput` on the widened double signatures. Back to 240.", "open_questions": [], "out_of_scope_findings": [ "filed as #12068: extend check:engine-double-contract with the findOne slice and adopt assertEngineFindOnePredicate across the remaining 209 files / 250 doubles — every file enumerated, per-package ledger breakdown, and the maintainer decision the 63 non-mechanical ones need (23 DEBT-ledgered + 40 unledgered)" ] }os-zhuang commentedon Aug 25, 2026
ACCEPT — PR #12072.
findOneslice to the gate's ownSLICES: 232 files / 275 doubles would newly fail; 169 already pinned for a write verb, 23 DEBT-baseline-only, 40 in neither ledger. Past a reviewable diff by a wide margin — and 63 of them cannot be closed by a dev seat at all, because the gate's own failure text declares a baseline entry maintainer-only. Landing the bounded slice was the right call, and the report says so explicitly rather than presenting a partial sweep as a complete one.where: []as NO predicate, which is exactly the#11767shape a truthiness copy accepts and the reason this class of defect shipped. Two residuals a double structurally cannot see are named in the module header instead of hidden./* comment */marker landed only in sourcemaps; the preflight caught it) and was reported rather than quietly redone; andcheck:query-options-erasurewent red at 240→242 and was fixed at the source, never by raising the ceiling — the single most tempting wrong move in a ratchet repo.Fixes #11957vsPart of: flagged rather than chosen silently.Fixesis right — this card asked for the guard plus a bounded adoption, and the remainder is its own card.packages/specin the diff (checked: 0 files), so no contract-review chain applies.Landing: enqueue once the remaining checks report green.
os-zhuang commentedon Aug 25, 2026
{ "issue": 11957, "status": "done", "branch": "claude/issue-11957-engine-double-findone-predicate", "pr": "https://github.com/objectstack-ai/objectstack/pull/12072", "premise_still_valid": true, "summary": "RESUME ROUND — the CI red is fixed and both previously-red contexts are green on the new head 885a32bc3. ROOT CAUSE, one error, exactly as the PM quoted: my new conformance test called `engine.registry.registerObject({ … })` with ONE argument, but the signature is `registerObject(schema, packageId, namespace?, ownership?, priority?)` — `packageId` is required, so it is a TS2554 'Expected 2-5 arguments, but got 1'. It was invisible locally and to the package's own `typecheck` because `packages/objectql/tsconfig.json` excludes `**/*.test.ts`; the ONLY reader of that layer is the shrink-only TEST_DEBT re-measure, which is why the sole symptom was 354 -> 355. FIX: pass 'test-package' (plus a comment saying why the one-argument spelling other doubles in this package still use is invisible), commit 885a32bc3. NOTHING ELSE TOUCHED: `scripts/check-type-check-coverage.mjs` is untouched, no TEST_DEBT entry was raised, and I did NOT run `--lower` for the unrelated `@objectstack/plugin-approvals: -1` note (it is still printed as informational, and it is not mine). THE SECOND RED WAS THE SAME ERROR, NOT A SEPARATE ONE — asked and answered from the job log: `TypeScript Type Check` is an AGGREGATOR over four lanes, and its whole failure text was 'type-check lane `typecheck-debt` concluded `failure` -- expected `success`' with typecheck-source-gates / typecheck-workspace / typecheck-consumers all `success`. One error, two red contexts.", "tests": "REPRODUCED THE CI PATH, not a proxy for it. `check:type-check-debt` had refused locally last round because the re-measure needs the full built closure; this round I built it exactly as lint.yml does — `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*'` → '70 successful, 70 total', 3m23s — and then re-ran the gate for real. (1) NARROW MEASUREMENT that located the error: rebuilt `remeasureProject`'s generated project for objectql (drop the test globs from `exclude`, keep its own `include`, default `typeRoots` from the package upward) and ran `tsc --noEmit -p <that config>`. BEFORE: 356 raw diagnostics, 1 of them TS6059 (the one the gate drops), i.e. 355 — matching CI's 355 exactly. Grepping the diagnostics by file attributed exactly ONE to my diff: 'packages/objectql/src/engine-findone-predicate.test.ts(65,19): error TS2554: Expected 2-5 arguments, but got 1.' AFTER the fix: 355 raw, 1 TS6059 → 354, equal to the ledger, and ZERO diagnostics attributed to that file. (2) THE REAL GATE, locally: `pnpm check:type-check-debt --re-measure` exit 0, its own verdict line 'check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 150.0s, 1898 raw tsc error(s) total, none above its recorded number.' `git status --porcelain` after it showed ONLY the one test file — no ledger write. (3) Behaviour unchanged: `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/engine-findone-predicate.test.ts` → 'Test Files 1 passed (1) / Tests 24 passed (24)'. `pnpm --filter @objectstack/objectql typecheck` exit 0. ESLint over the changed file: 1 file, 0 errors, 0 warnings. (4) Ratchet family re-run ON THE FINAL COMMIT 885a32bc3, tree clean: check:engine-double-contract 0, check:query-options-erasure 0, check:nul-bytes 0, check:type-check-coverage 0. (5) CI, BEFORE and AFTER, both contexts — this is the verification the PM asked for. BEFORE (head cc3697f8e): 'Type Check · debt ledger' = FAILURE (job 97723042289, '• @objectstack/objectql: TEST_DEBT records 354 raw tsc error(s), `tsc --noEmit` now reports 355 (+1)'); 'TypeScript Type Check' = FAILURE (job 97725612681, 'check-typecheck-lanes: FAILED (1 problem(s) above)' — the aggregator). AFTER (head 885a32bc3): 'Type Check · debt ledger' = SUCCESS (https://github.com/objectstack-ai/objectstack/actions/runs/32833208711/job/97756275305), its own verdict line from the CI log: 'check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 238.4s, 1897 raw tsc error(s) total, none above its recorded number.'; 'TypeScript Type Check' = SUCCESS (https://github.com/objectstack-ai/objectstack/actions/runs/32833208711/job/97758739847). The whole head is now green: 29 success, 2 skipped, 0 failing, 0 pending — including all six required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL)), each `success`. PR 12072 is still DRAFT and unmerged; I flipped nothing and touched no labels. DECLARED NARROWING, unchanged from last round: `scripts/pm/os-verify-lock.sh` cannot take the shared verify lock on this macOS host (no usable `flock`, declared Linux-only), so the local runs above were unserialized — declared, not silent. Prior-round evidence (the guard's ablation, the 24-file/306-test adoption run, the lint narrowing with its three pieces of evidence) stands unchanged and is in the PR body.", "open_questions": [], "out_of_scope_findings": [ "filed as #12068 (last round, still open): extend check:engine-double-contract with the findOne slice and adopt assertEngineFindOnePredicate across the remaining 209 files / 250 doubles — every file enumerated, per-package ledger breakdown, and the maintainer decision the 63 non-mechanical ones need", "NOT filed, reported only: `@objectstack/plugin-approvals` TEST_DEBT records 348 while tsc now reports 347 (-1). The gate prints it as an informational surplus and names `--lower` as the remedy. It is not mine and not this card's, so per the dispatch I left it alone; it is already tracked upstream as objectstack#6376, which the gate's own message cites." ] }os-zhuang commentedon Aug 25, 2026
ACCEPT (round 2 — the CI red is fixed) on PR #12072, head
885a32bc3. Verified againstcheck-runs, not the report: 0 failing, 0 pending, and all six required contexts readsuccess(Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance). Independently confirmed the ledger script is absent from the diff (0 files matchingcheck-type-check-coverage), so the forbidden shortcut was not taken.The defect was one line and the diagnosis is worth keeping: the new conformance test called
registry.registerObject({...})with a single argument against a(schema, packageId, …)signature — TS2554. It was invisible to the package's owntypecheckbecausepackages/objectql/tsconfig.jsonexcludes**/*.test.ts, so the only reader of that layer is the shrink-only TEST_DEBT re-measure — which is exactly why the sole symptom was354 → 355and why nothing local caught it. The dev reproduced the CI path rather than a proxy for it (built the full closure the waylint.ymldoes, then rebuilt the gate's own generated project and attributed the single diagnostic to the offending file), and the before/after counts land on the ledger number exactly.It also answered the question this seat asked rather than guessing: the second red was the same error, not a separate one —
TypeScript Type Checkis an aggregator over four lanes and its entire failure text was "type-check lanetypecheck-debtconcludedfailure", with the other three lanes green.Two restraints held under pressure, which is the part that matters in a ratchet repo: the TEST_DEBT entry was not raised (the gate declares that maintainer-only, "do not take this path to get CI green"), and the unrelated
plugin-approvals: -1surplus printed in the same output was left alone and reported rather than opportunistically closed.Round-1 verdict stands unchanged (inventory-first sizing, the bounded objectql slice, the enumerated 209-file remainder in #12068, the void-then-redone ablation, and the
check:query-options-erasureregression fixed at source rather than by raising the ceiling).Landing now: ready → merge queue.