Repository navigation
refactor(objectql): move the write-path reference check and the caller's master-detail header read out of engine.ts into engine-referential-integrity.ts (#25522 cut 10) - #26308
Conversation
…r's master-detail header read out of engine.ts into engine-referential-integrity.ts Move-only. The bodies of ObjectQL#assertReferencesResolve and ObjectQL#masterDetailHeadersCallerReads move unedited into packages/objectql/src/engine-referential-integrity.ts, with the module helper isEmptyReferenceValue that only the first calls. Both methods keep name, modifiers, signature, docblock and position; each body is now a host literal of exactly the members it reads (ReferentialIntegrityHost) and a .call. engine.ts drops the buildFieldError import, whose last reader moved. The two moved isSystem read sites re-key their rows in the system-context census page, and the federated injected-column readers table re-keys the assertReferencesResolve referenceTargetOf() row. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KiAsjPtReu1tmXvCEd8f7R
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
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 2dd7eb16df35bdd7e103c5696125893be41687e1 && git checkout 2dd7eb16df35bdd7e103c5696125893be41687e1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3249e204ad1b74ccb23f8295ab5132759418f872 bfeea141ca8187804760cad3b4af4222f02dd768 && git checkout -B drift-repro 3249e204ad1b74ccb23f8295ab5132759418f872 && git merge --no-ff bfeea141ca8187804760cad3b4af4222f02dd768
node scripts/docs-audit/affected-docs.mjs --json 3249e204ad1b74ccb23f8295ab5132759418f872
|
…ead census engine-referential-integrity.ts now holds the referenceTargetOf call that assertReferencesResolve made in engine.ts, so the census in second-object-read-exposure.pin.test.ts gets a not-a-served-read row for it, and the engine.ts row's `also` text drops the reference-existence probe, whose arbiter call no longer lives there. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KiAsjPtReu1tmXvCEd8f7R
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
Cut 10 of the
packages/objectql/src/engine.tssplit (#24586 ruling A, recorded in6270097840). The claim is6423785652. Two bodies of the "Referential integrity and master-detail resolution" section move unedited into a new module,packages/objectql/src/engine-referential-integrity.ts:assertReferencesResolve(the write path's #4441 reference check) andmasterDetailHeadersCallerReads(#22519, which master-detail headers the caller's read door returns). The module helperisEmptyReferenceValue, which only the first calls, moves with them. No behaviour changes. #25522 stays open for the later cuts.Branch
claude/project-thread-oed4lq(the card thread's harness-pinned branch), cut fromorigin/mainat366e4aa69, which is cut 9's merge (#26263). Nothing was edited before that merge landed.Why this area, and why not Autonumber
Measured on
366e4aa69with cut 9's script plus two screens it lacked.engine.tsis 23,040 lines, 7,384 of them from the last 30 days (git blame -s --since=2026-09-26T19:30Z).scripts/pm/git-history.mjs count: 128 commits in 30 days, 34 in 7.class ObjectQLholds 318 members, 255 methods and accessors.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…createThe full per-method screen and both price lists are in the project file
pm-orders/server/25522-cut10-seam-table.md. The stay rules are cut 9's (inferred-return public, a U site, a private-static read, anobjectql/src/engine.ts#NAMEpointer underpackages/spec/**ordocs/adr/**, an engine self-call, a static), plus two new ones:query-options/no-any-erasuresite stays.scripts/query-options-erasure-baseline.jsonis shrink-only in its key set ("adding a file here is not a sanctioned move"), so a new module cannot carry one. Lintingengine.tsunder an ungrandfathered file name finds the baseline's 8 sites, inseedAutonumber,referenceExists,resolveMasterDetailParent,resolveMasterDetailParents,inspectDanglingReferences,find,findOneandprobeReferencingRows.engine.tsmoves only when every user of that helper (and of that helper's own helpers) moves too. Otherwise the module would value-importengine.ts, a cycle at run time.assertReferencesResolve79,masterDetailHeadersCallerReads23, helperisEmptyReferenceValueisIssuedAutonumberCollision5,warnAutonumberReadersUnserved13seedAutonumberholds an erasure site;adoptExplicitAutonumbersharesreadStoredAutonumberCounterwith it, and spec prose (packages/spec/src/data/autonumber-format.ts) names that helper inengine.ts, which this claim may not editaccessibleOrganizationsOfIn Referential integrity the rest stays:
referenceExists,resolveMasterDetailParentandresolveMasterDetailParentsread the private staticreferenceCheckContextand hold erasure sites;settleMasterDetailLandingcallsstaticReadonlyStripTakes, whosestaticReadonlyStoredViewupdatealso uses;resolvePreviewParents(masterIdOf,servedAsStored) andresolvePredicateRelated/resolveOneHopRelated(undeclaredRelatedFields) share helpers with bodies that stay;masterDetailHeaderExists,materializeParentHeaderandmasterDeclaredFieldsare no longer than their stubs would be.The shape
private async), signature, docblock and position. Each body is now one host literal of exactly theReferentialIntegrityHostmembers it reads, plus a.call, marked[#25522 cut 10], as cut 8's stubs are:assertReferencesResolvegetsmasterDetailHeaderExistsandreferenceExistsas late-bound closures;masterDetailHeadersCallerReadsgetsfindas a closure andloggeras a getter.ReferentialIntegrityHosttypes each member as the class member (ObjectQL['find']and so on); each function'sthisis thePickof what it reads. The module imports the engine only as a type.isEmptyReferenceValuemoves with its docblock byte for byte (508 bytes, 12 lines) and is not exported; its only user wasassertReferencesResolve. One copy, no re-derivation.referenceTargetOfandEngineQueryOptions(@objectstack/spec/data),ExecutionContext(type), andValidationErrorandbuildFieldError(./validation/record-validator.js), the bindingsengine.tsused.engine.tsdropsbuildFieldErrorfrom its import (its last reader moved;tscreported TS6133) and gains the new module import with a three-line comment.Net lines (the seat's rule)
engine.tsgoes from 23,040 to 22,942 lines, net -98 (18 added, 116 removed). The module is 200 lines. With the two re-keyed census rows and the one re-keyed readers row, the diff is 340 changed lines over 4 files (+221 / -119).The published
.d.tsdoes not change (ruling6348891160)Base:
@objectstack/objectqlbuilt withengine.tsrestored from366e4aa69(blobc9773e3achecked) and the new module removed; restored withgit checkout HEAD --, blob hashes compared,git diff HEADempty. Head: built atb1f3252c8.masterDetailHeadersCallerReads(masteroccurs 2 times in the basedist/index.mjsand 3 times in the head's, so each leg built the source it names. All six declaration files are byte-identical, and equal to the hashes cuts 7 to 9 recorded:core.d.mtsef178bf0725eccc2f358f082ce13dc5122a6d5a53c19ef630017256f67de03b4core.d.ts1d20303a455a909e4b55fd0802fb0b0bcb59ec51784daf27d5ef4c0659410953index.d.mtsccf900240aa137abe72f82b8abfc872d42c5645467400134c580285f2a3b3090index.d.ts0454d5e70b156d8effbf27b8915587c7cbee241dcd7d698013c0a4c369a02aa5util-CVzOGxAn.d.mts85ef1cbec2b016d5f7afabaa21689c86c6dffc573076fdfdeb8ea0f1a1b1ad53util-CVzOGxAn.d.ts85ef1cbec2b016d5f7afabaa21689c86c6dffc573076fdfdeb8ea0f1a1b1ad53Positive control: dropping
privatefrom themasterDetailHeadersCallerReadsstub (anchor landed once, 0 private hits after) and rebuilding changed all six hashes and the chunk name (util-Cz_Kcdws.d.tse762bbf9…,index.d.ts5f1e19b4…), and the chunk now printsmasterDetailHeadersCallerReads(master: string, ids: ReadonlyArray of unknown, context: ExecutionContext | undefined). Restored withgit checkout HEAD --(blob hash compared,git diff HEADempty, the private spelling back to 1 hit), rebuilt, and all six files compared equal to the head build again.Proof of move-only
A TypeScript-AST leaf-token compare (
pm-orders/server/evidence/25522/astcmp.mjs, unchanged from cut 9) of baseengine.ts(366e4aa69) against the head module:async, and every body line equals the base line less two leading spaces, comments included:assertReferencesResolve285 tokens / 79 lines,masterDetailHeadersCallerReads239 / 23.isEmptyReferenceValue: the base text from its docblock to its closing brace is a substring of the module, andengine.tsno longer declares it.record-validator.jsimport, lessbuildFieldError;isEmptyReferenceValue, moved; andmasterIdOf, whose own text and docblock are identical (getTextequal) but whose leading trivia now starts with theimplements IObjectQLEnginecomment that used to sit aboveisEmptyReferenceValue's docblock. That comment describes the class, so it stays inengine.tsunedited. Head adds the new module import with its comment.Readers of the moved lines
isSystem, so rows 23 (assertReferencesResolve) and 29c (masterDetailHeadersCallerReads) ofcontent/docs/permissions/system-context.mdxnow anchorpackages/objectql/src/engine-referential-integrity.ts#NAME; their wording is unchanged.check-system-context-censusat base: 153 read sites across 81 files, 145 of 145 symbols cited, OK; at head: 153 across 82 files, 145 of 145, OK. Control: with the base page over this head the gate exits 1 with exactly three findings:file-without-a-rowfor the new module andanchor-is-not-a-read-sitefor each old anchor. Restored fromHEAD, blob hash compared, green again.federated-injected-column-readers.test.tskeys rows byFILE#FUNCTION :: SEAM, andassertReferencesResolvecallsreferenceTargetOf, so its row is re-keyedengine-referential-integrity.ts#assertReferencesResolve :: referenceTargetOf(); disposition and reason unchanged. Head: 5 of 5 pass. Control: the base file over this head fails 1 of 5 and names the new key; restored, blob hash compared.scripts/adr-anchors/entry describes a moved body (the engine entry's one paragraph is the transaction seam). The moved bodies cite no decision letter, and the docblocks that cite ADR-0131 D1 and ADR-0055 stay on the methods.check-adr-anchorsat base366e4aa69(clean detached worktree): 90 anchored files, 1,880 decision-letter citations, 59,261 resolving citations; at head: 90, 1,880, 59,261.objectql/src/engine.ts#NAMEpointer underpackages/spec/**ordocs/adr/**names the two or the helper.docs/qa/platform-checklist/areas/records-forms.jsoncitespackages/objectql/src/engine.ts#assertReferencesResolvetwice as evidence; the method is still declared there with the docblock the citation quotes, andcheck-platform-checklist --changed-from origin/mainjudges 13 anchors in the change set, 0 lost. Prose that names the methods (service-messagingandservice-settingsactor-reference.ts,plugin-securityposition-catalog-refusal.ts, test comments) still resolves, because both methods stay on the class.durability-read-invention.baseline.jsonkeys onlyreferenceExists, which stays;query-options-erasure-baseline.jsonkeepsengine.tsat 8, sincemasterDetailHeadersCallerReadsspellsas EngineQueryOptions).Acceptance notes
pr_create: no open PR touchespackages/objectql/src/engine.ts,system-context.mdxorscripts/adr-anchors/. feat(spec,cli)!: retire the ≤16.x retirement kits; the strict unknown-key refusal names the protocol upgrade guide #26138 editsvalidation/rule-validator.test.tsonly.origin/mainmoved 10 commits after the base, none onpackages/objectql/**,content/docs/permissions/**,scripts/adr-anchors/**or the readers test, so the branch was not merged again.driverWriteTargetmoves withengineLayer0VerdictsoaccessibleOrganizationsOfstays module-local). Migration flags still needs a setter ruling.Verification
At
b1f3252c8:pnpm --filter @objectstack/objectql typecheck(tsc --noEmit+check:test-typecheck): VERDICT command-exit 0.pnpm exec eslint --no-inline-config packages/objectql: exit 0.pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 472 files, 8,789 tests passed (VERDICT command-exit 0)..d.tsfiles are byte-identical, so no dependant can see a difference. CI'sTypeScript Type Checkcovers them.dispatch-gates --commandsderived 90 families on the real diff. Every one was run, each exit code captured before any pipe, then reconciled with--ran: 90 accounted for, 88 run green, 2 NOT MEASURED (exit 3, PREREQUISITE NOT MET; CI runs both):check:dual-build-cjs-loads(needs every package'sdist/) and@objectstack/spec check:skill-examples(needs the full build). Three first-run exit codes were prerequisites, re-run green after meeting them:check-engine-split-ratio --days 90(shallow clone; deepened to 2026-07-21, 97.6%),check-plugin-teardown-shape --self-testandcheck:lean-entry-closure(needed the objectqldist/). Named readings:check-system-context-censusOK,check:query-options-erasure,check:durability-log-level,check:org-identifier,check:logger-receiver-detach,check:doc-anchors,check-affected-docsexit 0.b1f3252c8:check-adr-anchorsat base and head (above),check-adr-symbol-anchors,check-tenant-audit-census,check-platform-checklist --changed-from origin/mainandcheck-commit-card-trailers --range origin/main..HEAD.test:repowas not run locally; CI'sTest Coreruns it.skip-changeset: move-only. The published.d.tsfiles are byte-identical,liveness/and every other published path are untouched, and the module's runtime is the same code under a different file name. The docs edit changes only the anchor path in two rows of the census table.Generated by Claude Code