fix(objectql): refusals, log lines and metadata text state each decision in words instead of a tracker number (stage 3) - #20848
Conversation
…ecision in words instead of a tracker number (stage 3, part 1) The engine's unknown-option and filter-array refusals, the two bulk-write row-scoping refusals, the credential-aggregation refusal, the HAVING operator refusal, the empty hook target, the hook-target rebind refusal, the strict read-only refusal, the system-write organization refusal, the lifecycle retention-override setting description and the search companion field description no longer cite tracker numbers. Where the sentence did not already say what was decided, it now does. The findOne refusal keeps its citation: metadata-core's byte-identical twin is compared in this package's tests, and that twin is a later stage's row. Text only. The prose-id ledger is recomputed with --census-ledger. Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
…acker number (stage 3, part 2) The ADR-0104 value-shape gate lines, the non-atomic cascade warning, the system-ledger transaction carve-out, the dangling-reference audit summary, the delegated protocol assembly line, the legacy apiMethods warning, the read-only and runtime-owned strip warnings and the two unevaluable-rule warnings no longer cite tracker numbers. Where the sentence did not already say what was decided, it now does. Two tests that pinned a number now pin the sentence. Text only. The prose-id ledger is recomputed with --census-ledger. Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 17 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 310e1c07b5d5b40c33182a5f6312e84879e1a0fd && git checkout 310e1c07b5d5b40c33182a5f6312e84879e1a0fd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 30839063b56181e396ddd3065cab47e6d884dc66 26848968cc5bd863ddcc6d9acf106afefee523d3 && git checkout -B drift-repro 30839063b56181e396ddd3065cab47e6d884dc66 && git merge --no-ff 26848968cc5bd863ddcc6d9acf106afefee523d3
node scripts/docs-audit/affected-docs.mjs --json 30839063b56181e396ddd3065cab47e6d884dc66
|
Contract reviewServed-tier: Read-only, at tier, adversarial to the dispatch. Inputs: card #20513 (body and all 21 comments — the census 5900801368, the decision request 5902063603, the ruling 5902360492 「20513 A」 A / A, the lane checklist 5902678544, the stage-3 claim 5909669496, the stage-3 Check-runs on ① Derived judgmentsScope against the ruling and the claim — right. The ruling orders stages per package, all three categories, form D, Text only — right, checked hunk by hunk. Every removed and added line in the 12 source files is a string-literal line, with two exceptions the dev names and I confirm: Accept-set and public surface — nothing moves — right. No schema, filter grammar, route, wire Ledger diff — right, exact. One hunk, the Form D, string by string — right. All 31 cited cards read
Where only the citation goes (29 literals): each sentence already carried the decision and is otherwise unchanged, and each card's title matches its sentence — the unknown-option refusal (4371, "silently drops sort/select/skip/populate"); the two filter-array refusals (5158, the array dialect settled as input-only sugar lowered before any driver); the credential-aggregation refusal (3171 / 7922); the HAVING unknown-operator refusal (4286); the empty hook target (4001); the hook-target CLEARED branch (5574 / 5846); the strict read-only refusal tail (3407) and the Every kept ADR says what its sentence claims — right. ADR-0100's non-goals paragraph names the aggregate masking gap and says "the correct fix is to reject aggregations that reference a credential field" (the credential refusal). ADR-0078 is the no-silently-inert-declaration decision the HAVING refusal applies ("refused rather than ignored"). ADR-0104 is the value-shape contract with the per-deployment verification flags and Pins — right, 6 lines in 3 files. Review faces — right. The changeset (below) reads as a CHANGELOG entry; every family it names is in the diff and every sentence it summarises matches the string. The PR body's table, counts and commit order match the diff (16 + 23 = 39 literals, 19 + 23 = 42 occurrences; eight ADR-0104 lines; eight strip clauses), it carries One wording note, not a FAIL item. ② Semver level
Clause-②: no — right. Nothing an author can write is widened or narrowed; the PR body carries ③ Boundary flagsDev flags (report 5910562542), each answered:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #20513
Clause-②: no
Stage 3 of 5 of this lane (
objectql), under the maintainer's A / A ruling on the card. The card stays open for stages 4-5, so this PR carries no closing keyword. Text only: no errorcode, field name, HTTP status, export or control flow moves. Every changed source line is a string-literal line, except two single-line log calls (port.warn?.(...)in the dangling-reference audit andctx.logger.info(...)inplugin.ts) whose only changed token is the literal; stripping every string literal from the removed and the added lines leaves the same code skeleton apart from+joins for re-wrapped literals.What this does
The query engine's refusals, metadata text and log lines sent the reader to a tracker number for the reason behind them. Each rewritten string now says that reason in words (form D, as the migration-entry rewrite and stages 1 and 2 applied it). Where the sentence already stated what was decided, only the citation goes. Where it did not, the decision is added in words:
engine.tsbulk update and bulk delete row-scoping refusals (thrown,[Security])hook-target-rebind-errors.tsby-id REBOUND branch (thrown)delete()used to honour a rebind by re-resolving the new target; that is retired too, because a handler that silently redirects which row gets deleted is a trap.hook-target-rebind-errors.tsunscoped-multi branch (thrown)dispatchUnscopedMultiWrite, on update and delete alike.engine.tsnon-atomic cascade warning (warn)engine.tssystem-ledger write inside a transaction (debug)integrity/dangling-reference-audit.tssummary (warn)registry.tslegacyapiMethodswarning (warn)validation/rule-validator.tspredicate and when-predicate evaluation failures (warn)retention_overridessetting description and the search companion field description (metadata text); the eight ADR-0104 value-shape gate lines; the delegated protocol-assembly line; the read-only and runtime-owned strip warnings (eight clauses)Each claim was checked against today's code, not only against the cited card: the bulk-write AST is seeded before
executeWithMiddlewareon both verbs;planCascadeAtomicityreturns'atomic'for a single-datasource cascade anddelete()then runs it insidetransaction(); a cross-datasource business write throwsCrossDatasourceTransactionWriteErrorwhile a system-ledger object runs outside the transaction; the write-path reference check is non-system writes only;LEGACY_API_METHODSholds the eight derived or retired values;dispatchUnscopedMultiWriteis a registration option valid onbeforeUpdate/beforeDelete; an unevaluable rule returnsunevaluableRuleError. All 31 cited cards read closed as completed.One string held for a later stage
engine.ts'sfindOneno-predicate refusal keeps its citation (4419, 1 occurrence).@objectstack/metadata-core'sengineFindOnePredicateRefusalMessageis documented as byte-identical to it, and this package'sengine-findone-predicate.test.tscompares the two (strict equality andtoThrow). Moving the metadata-core copy is another package's ledger row, which this stage may not touch. A one-off ablation proves the hold: dropping the citation inengine.tsalone turns 9 of 24 tests in that file red. Stage 5 (the other seven packages,metadata-coreamong them) rewrites the pair together, and its ledger diff will move thisobjectqlrow too.Order inside the stage
All 39 rewritten literals (42 occurrences) fit one PR, under the stop line, so the stage lands whole in three ordered commits, each recomputing the ledger so every commit is green on
check:doc-authoring:3432973ffauthor-visible text: 16 literals (19 occurrences), the thrown refusals and the two metadata texts, plus the one byte-exact re-pin;bccd37a25log lines: 23 literals (23 occurrences), plus the two re-pinned tests;26848968cthe changeset.objectqlhas no ledgeredsrc/-shipped test string: the census's 3 test-facing strings sit inengine-data-events.bench.ts, which the ledger excludes.Pins re-pinned: 6 assertion lines in 3 test files
integrity/dangling-reference-audit.test.ts377, 498, 715, 959 found the summary warning by the number. They now find it by "reported, never rewritten".registry.test.ts1201 asserted the number in the legacyapiMethodswarning. It now asserts "derived from them or retired".engine-dropped-fields-primary-key.test.ts318 is the byte-exact pin on the strict read-only refusal; it moves with the text and stays byte-exact.A one-off mutation proves each moved pin can fail, run on the committed head
26848968ccwithscripts/ablation-replace.mjsin wrap mode (anchor hit once, disk-verified) under a shell trap that restores each file fromHEAD. The tests importsrcdirectly, so no build leg applies. "reported, never rewritten" to "reported, never repaired": 4 failed / 35 passed. "derived from them or retired" to "derived from them or dropped": 1 failed / 100 passed. "onFieldsDropped instead." to "onFieldsDropped instead!": 1 failed / 15 passed. After each leg the blob equalsHEADandgit diff HEADis empty; after the rungit status --porcelainhas 0 lines.No consumer outside the package pins a changed string. Every fragment of every removed source line was searched in all test files of the repository: the hits are comments, fragments the new text keeps, the package's own tests above, and
plugin-security'sauto-org-admin-grant.test.ts, whose test double keeps its own copy of the unknown-option sentence and is never compared with the engine's (see Acceptance notes).Ledger burn-down
scripts/doc-authoring-prose-id.baseline.jsonwas regenerated with--census-ledger. Onlyobjectqlrows move; no other package's row changes.src/engine.tssrc/validation/rule-validator.tssrc/hook-target-rebind-errors.tssrc/readonly-strict-errors.tssrc/tenancy/system-write-organization.tssrc/search-companion.tssrc/registry.tssrc/plugin.tssrc/lifecycle/lifecycle-settings.tssrc/integrity/dangling-reference-audit.tssrc/hook-binder.tssrc/having-filter.tsThe census's 38 messages for
objectqlare the 40 ledgered literals minus the two metadata texts it bucketed separately; the 40 literals carry 43 occurrences because three of them cite two numbers each.Verification (head
26848968cc)turbo run buildover@objectstack/objectqland its closure, 14/14; then the whole workspace (./packages/*,./packages/*/*), 71/71; then@objectstack/objectqldirectly after the ablation run touched its sources. The new sentences are indist/index.js, and none of the removed citations remains in a string there (the matches left are comments).@objectstack/objectqltests (vitest run --project local --maxWorkers=2, three shards): 115 + 115 + 114 files passed, 2253 + 2001 + 2515 tests passed, 0 failed.@objectstack/objectqltypecheck: exit 0 (tsc --noEmit, the scripts config, andcheck:test-typecheckholding its ledger).node scripts/pm/dispatch-gates.mjs --commands: 76 derived commands, all run, all exit 0.check:dual-build-cjs-loadsandcheck:type-check-debtfirst answered exit 3 (prerequisite not met) and were re-run after the full build and the directobjectqlrebuild;check:dts-closureandcheck:lean-entry-closurewere re-run there too.--ran: 76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN.check:doc-authoring: sibling-package prose ids hold the baseline, no growth, no burn-down unrecorded (647 pinned sites across 204 files).eslint --no-inline-config --format jsonover the 15 touched.tsfiles: 15 files, 0 errors, 0 warnings. The resolvedparserOptionsforengine.tsareecmaVersion: latestandsourceType: moduleonly, with noprojectand noprojectService, so no type-aware rule can move an untouched file. Repo-widepnpm lintis CI's.repovitest project's one file (action-owner-key-single-source.test.ts, untouched by this diff), the Test Core shards, Dogfood, and the workspace type-check lanes.Acceptance notes
findOnerefusal pair (objectqlengine.tsandmetadata-coreengine-findone-predicate.ts), as above. Stage 5's file surface needspackages/objectql/src/engine.tsas well.plugin-security'sauto-org-admin-grant.test.tscarries a test double of the engine's unknown-option refusal with its own copy of the sentence, citation included. It is a test file, outside the ledger and outside the ruling's shipped-src/scope, and no test compares it with the engine's text, so the two now differ by the citation only. Carrier: none.Generated by Claude Code