Repository navigation
refactor(objectql): move the validation-message i18n helpers out of engine.ts into engine-validation-messages.ts (#25522 cut 1) - #25615
Conversation
…ngine.ts (#25522 cut 1) Move-only. The bodies of ObjectQL#validationMessageContext, #negotiatedMessageLocale and #objectDisplayLabel move unedited into engine-validation-messages.ts; each method keeps its name, signature, docblock and position and hands its body the members it reads through ValidationMessageHost. setI18nService stays in the class. Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 19 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 beb5c8f8e8e5434e0e67f8f9198620243aa839fd && git checkout beb5c8f8e8e5434e0e67f8f9198620243aa839fd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e649c2d91e270e3da26820bcfe54a28445305a75 328b5b194ecc2c00273f13f4ab84a6d9957548b4 && git checkout -B drift-repro e649c2d91e270e3da26820bcfe54a28445305a75 && git merge --no-ff 328b5b194ecc2c00273f13f4ab84a6d9957548b4
node scripts/docs-audit/affected-docs.mjs --json e649c2d91e270e3da26820bcfe54a28445305a75
|
席位核对ACCEPT · Checklist conclusions (read on GitHub, not from the report):
Deviations accepted: 1–6 as reported. On deviation 3: every moved member is private and the declarations are byte-identical, so no dependant's input changed. CI's Type Check lanes cover the rest. The open question (single-quoted unions in later cuts): no new ruling is needed. Ruling 6348891160's own text sets the series bar as "identity after sorting string-literal union members, plus the same export list". A single-quoted literal is a string literal, so a later cut whose raw diff is only a member-order swap inside a single-quoted literal union meets the ruled bar. The quoted one-liner is the double-quote spelling of that bar. The cut that needs the single-quote spelling states it in its own proof block, with the same positive control. This is the seat's reading of the ruling's text, recorded here so the maintainer can overrule it with one word; it adds no script and no gate. Carried to the next slice of #25522 (not filed): the dev proposes Datasource routing as cut 2 (measure Landing: Generated by Claude Code |
Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
Cut 1 of the
packages/objectql/src/engine.tssplit (#24586 ruling A, recorded in6270097840). The claim is6382124735. ThreeObjectQLmethod bodies move unedited into a new module,packages/objectql/src/engine-validation-messages.ts: the validation-message localisation helpersvalidationMessageContext,negotiatedMessageLocaleandobjectDisplayLabel(all private).setI18nService, the public setter of the field they read, stays in the class: it is a two-line field write, and moving it would grow the class rather than shrink it.It is move-only, and the card stays open for the later cuts. Branch
claude/issue-25522-objectql-engine-cut-1, cut fromorigin/mainate649c2d91e;mainhas not moved since.Serial with the file's in-flight PRs. At start, none of the 12 open PRs touched
packages/objectql/**(each PR's file list read). Re-measured just before this PR was opened, on all 14 open PRs: none touchespackages/objectql/**.Why this area for cut 1
The fresh seam table below has six areas with zero or one line from the last 30 days. This one is the only one that meets every series limit and has no path reader outside
packages/objectql:recomputeSummarieshas an engine write (this.update() and handsthistoaggregateSummaryValue, so it would have to stay. And everysummaryOperationsmention inengine.tssits inbuildSummaryIndex, which the liveness ledgerpackages/spec/liveness/field.jsoncites as evidence by path, so moving it pulls apackages/specedit into the slice.initandsetDatasourceMapping, which must stay.this.FIELD.delete/insert/update(sites (theUNTYPED_RECEIVERSlimit).The shape (#23050 shape B, as #25222, #25223, #25224 and #25467 apply it)
.d.ts. Each body is now one host literal plusfn.call(host, …), marked[#25522 cut 1].objectDisplayLabelreads no member and is called directly.ValidationMessageHostis new. It types every member as the class member itself (ObjectQL['i18nService'],ObjectQL['negotiatedMessageLocale']). Each moved function types itsthisas thePickof the host over exactly the members its body reads, and the stub annotates its literal as theThisParameterTypeof the moved function. No cast, and no member becomes public or protected.negotiatedMessageLocalegoes in as a late-bound closure ((...a) => this.negotiatedMessageLocale(...a)), so a spy or double set on the instance is still the onevalidationMessageContextreaches.i18nService, goes as a getter. That includes the read inside thetranslateclosure, which runs aftervalidationMessageContexthas returned and still sees whatever service is installed at call time, as before.ObjectQLas a TYPE only, so there is no module cycle at run time. It importsobjectLabelKeyandresolveBundleLocalefrom@objectstack/spec/system, the specifierengine.tsused. Those two names had no other reader inengine.ts, so they leave its import list: that is the only edit outside the class besides the new module import. Nothing re-exports the module.The published
.d.tsdoes not changeUnder the series bar (ruling
6348891160). Base was aturbo run build --filter='@objectstack/objectql...'ate649c2d91e(14/14 tasks). Head waspnpm --filter @objectstack/objectql buildat328b5b194eagainst the same dependency tree.@objectstack/objectqlemits six declaration files:index,coreand a shared chunkutil-ByiLm3B6(wheredeclare class ObjectQLlives), each as.d.ts(CJS) and.d.mts(ESM). The chunk name is the same at base and head. All six are byte-identical, and the raw diff of each is 0 lines:index.d.ts9ca22071717e3f761198115fa54a143df4151a52950d826a10677420dcad16ae9ca22071717e3f761198115fa54a143df4151a52950d826a10677420dcad16aeindex.d.mts69dd1cfe3875024f434b2d7defce771ebc8c7e48a05be7c946fa6ecdbd912b4069dd1cfe3875024f434b2d7defce771ebc8c7e48a05be7c946fa6ecdbd912b40core.d.tsd27b72bde2eec9a47c7dd3c104976321533cf397452ca9f84491a455ec28e73bd27b72bde2eec9a47c7dd3c104976321533cf397452ca9f84491a455ec28e73bcore.d.mts5a0cca505296b678b31c742c285e028fe434f5467bb8d27e052e470ce0e096525a0cca505296b678b31c742c285e028fe434f5467bb8d27e052e470ce0e09652util-ByiLm3B6.d.tsand.d.mts1742a9cb029654d1ca4e8e392e98001eec85963987f516b1b76f672ecd08e5a91742a9cb029654d1ca4e8e392e98001eec85963987f516b1b76f672ecd08e5a9The normaliser, verbatim from the ruling, run on each file:
util-ByiLm3B6.d.ts: line 7261private objectDisplayLabel;made public (objectDisplayLabel;, one line differs) gives raw = normalised270f913f734273e380af6ab964e8b15206aa7c511892dea58cc8f6fd9adf5ef7, which differs from1742a9cb….import()ofdist/*.mjs,requireofdist/*.js), are identical at base and head:.exports 182 names under ESM and 182 under CJS, and./coreexports 54 and 54. The sorted lists hash881616cc179189098182bd6b8a95701d9922f9677b786bc12e2d31c1adb0adaa(.) and393e806906e876c2e020aa96617d54050a35ae63cd483b67d8e968b7b5390f49(./core) at both ends.No changeset:
skip-changeset, per ruling6348891160item 4.Proof of move-only
getChildrenleaves): each moved function's parameters (less the addedthis), return type and body have the same token sequence as the base method, and each body line is the base line with two leading spaces removed. 371 tokens compared, 0 differ. Negative control: one changed token innegotiatedMessageLocalemakes the check report a difference.engine.ts: 232 of the 233 statements outside the class are byte-identical. The other is the@objectstack/spec/systemimport, lessobjectLabelKeyandresolveBundleLocale. The one new statement is the import of the new module.engine.tsgoes from 23,903 to 23,881 lines. Each stub keeps its docblock and carries its host literal, so the file shrinks by less than the bodies' length.engine-validation-messages.tshas 118 lines.negotiatedMessageLocale(itsresolveBundleLocale(...)return replaced byreturn requested;, written withscripts/ablation-replace.mjs) turnsengine-validation-locale-negotiation.test.tsred, 3 failed / 3 passed. It was restored to the HEAD blob5d9194fa75bbwithgit diff HEADempty, and no test file was kept.Readers of the moved lines
scripts/adr-anchors/packages__objectql__src__engine.ts.jsoncarries one paragraph, on the transaction seam (ADR-0057, ADR-0067, ADR-0119), and none of it describes a moved body. The moved bodies cite no ADR (the ADR-0053 Phase 2 reference is invalidationMessageContext's docblock, which stays on the stub). So the new module owes no anchor entry, and none is added.check:adr-anchors: 84 → 84 anchored files; decision-letter citations on the anchored surface 1,883 → 1,883; 52,465 citations across 6,583 files at both ends.git greprepo-wide): none pinsengine.ts#validationMessageContext,#negotiatedMessageLocaleor#objectDisplayLabel. The mentions are prose:plugin-approvals/src/reader-locale-labels.tsandservice-automation/src/engine.ts(by method name, which still resolves), andspec/src/system/translation.zod.ts(engine.ts'ssetI18nService, which did not move).check-adr-symbol-anchors: 2,384 anchors resolve at head.core-boundary.ratchet.test.ts: the new module joins the/coreclosure and imports only@objectstack/spec/system. It passes.Seam table (fresh,
origin/maine649c2d91e, 2026-10-24)engine.tsis 23,903 lines. 8,253 of them (34.5%) come from the last 30 days (git blame --since=2026-09-24T08:00Z, with history deepened to 2026-09-15). 124 commits touched it in 30 days, and 33 in 7. The file has the module preamble (lines 1–5014, 2,225 lines from the last 30 days),class ObjectQL(5015–23228, 317 members, 255 methods: 147 private, 108 public), and a trailer (23229–23903, 107). Sections are contiguous member ranges, docblocks included. The columns: "inferred public" lists public methods with an inferred return type (these stay), "U" countsthis.FIELD.delete/insert/update(sites, and "private statics" lists static members that are not public (these stay).txStore…ConstructorgetStatus…registryregisterHook…triggerHookshasHooksFor…assertBulkPerRowHookBudgetregisterAction…setActionActive_heldFileResolver…registerInsertRecomputeListenerreadableIdsSecurityReported…resolveDefaultPermissionswriteEpoch…hasObjectMiddlewareexecuteWithMiddleware…buildUserbuildDriverOptions…resolveEnginePostureprobeInstallOrganizations…transactionCoversDriverForresolveNowDefault…defaultFieldEntriesapplyAutonumbers…seedAutonumberregistrationConversionWarned…registerDriversupplyFileColumnsMovedResolver…listUnavailableDatasourcesassertWriteAllowed…publishBulkDataEventsetI18nService…objectDisplayLabelassertReferencesResolve…masterDeclaredFieldsinspectDanglingReferences…inspectCascadeDeletesetCryptoProvider…setTenancyPostureProviderencryptSecretFields…resolveInternalFieldgetSchema…judgeFilterLIFECYCLE_DATASOURCE…setDatasourceMappinginit…resolvePrimaryDatasourceobjectHasMediaField…valueShapeStrictForisFileReferencesMigrationVerified…wasDatastoreCreatedFromEmptydestroy…destroyMAX_EXPAND_DEPTH…MAX_BULK_PER_ROW_HOOK_ROWSautonumberCounters…recomputeSummariesexpandRelatedRecords…expandRelatedRecordsresolveFileReferences…readParentDerivedFilesresolveWhereTokens…requireFindOnePredicatefind…staticReadonlyCreateStripvalidate…validatereadUpdatePriorRow…insertManyupdate…updateplanCascadeAtomicity…organizationOwnedRowCountsdelete…deletecount…aggregateexecute…enforceTransactionOriginregisterObject…createCut 1 is the Validation-message i18n row, less
setI18nService.For the next dispatch (cut 2)
setDatasourceMapping(inferred return, stays) and the two public statics. That isisSystemLedgerObject,getDriver,resolveDatasourceBinding,resolveEffectiveDatasource,resolveMappedDatasource,resolveDatasourceFromMappingandmatchPattern. Its 343 lines have 1 line from the last 30 days, nothis.FIELD.delete/insert/update(site and no private static. The anchor entry's paragraph names "the same tuple ADR-0057 §3.6 routing reads", so the new module would get its own anchor entry carrying it. MeasuregetDriver's spies and doubles before choosing it.packages/spec/liveness/field.jsonevidence pointer moved in the same slice, andrecomputeSummariesstays..d.tsprints literals in single quotes, so the ruling's verbatim normaliser cannot absorb a union-member-order swap here. A cut that moves a body checked before the rest ofengine.tsand reorders a union would show a raw diff that the normaliser leaves in place. Under the ruling's item 1 that is a stop: the normaliser must not be widened. The decision is raised in the os-dev report on the card.Verification
pnpm --filter @objectstack/objectql typecheck(tsc --noEmitandcheck:test-typecheck): exit 0.eslint --no-inline-config packages/objectql: exit 0.packages/objectqlsuite at head328b5b194e(vitest run --project local): 466 files, 8,700 tests, 8,700 passed, 0 failed, 0 skipped. The suites that pin these helpers (engine-validation-locale.test.ts,engine-validation-locale-negotiation.test.ts,engine-delete-restricted-locale.test.ts) are among them.TypeScript Type Checkcovers the dependants.dispatch-gates --commandsderived 56 families on the real diff. Each was run at head, and its exit code was recorded and reconciled with--ran(56/56 accounted for). 53 exit 0. Three are NOT MEASURED locally, and CI runs them:check-engine-split-ratio --days 90exit 2, CANNOT COMPUTE: the shallow clone's history starts 2026-09-15, inside the window.check-plugin-teardown-shape --self-testexit 3: the pinned fixture commit is unreachable from this shallow clone.check:dual-build-cjs-loadsexit 3, PREREQUISITE NOT MET: it needs every package'sdist/. The export lists above are its question for this package.check-adr-anchors,check-adr-symbol-anchors,check-nul-bytes,check-platform-checklist --changed-from origin/main.Generated by Claude Code