Repository navigation
refactor(metadata-protocol): move the query validation out of protocol.ts into protocol-query-validation.ts (#25467 cut 3) - #25538
Conversation
…l.ts into protocol-query-validation.ts Move-only. The class keeps the nineteen private query-validation methods (assertObjectRegistered, the assert*FieldsExist family, the refuseInternal* walks, servedExpand and their helpers) with their docblocks, signatures and positions; their bodies move unedited into the new module and are handed the members they read through host literals (QueryValidationHost). The eleven module-level declarations only those bodies read move with them, unedited. The ten that code staying in protocol.ts also reads gain export in place; index.ts exports protocol.js by name, so none of them is published. The bracketed-refusal pin follows the 17 moved refusal sites with a PRODUCERS row for the module. Claude-Session: https://claude.ai/code/session_01J8pUyVs5eas1AjMdReJM7C Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 39 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 11 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 f87625cf80196d64e9926c1e45fbf01d93acd226 && git checkout f87625cf80196d64e9926c1e45fbf01d93acd226
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 74496c64b0637103f87cceec3f37932a48813e65 3b1ae504137ea60dfb810bd4d25c68eb57a4f3f5 && git checkout -B drift-repro 74496c64b0637103f87cceec3f37932a48813e65 && git merge --no-ff 3b1ae504137ea60dfb810bd4d25c68eb57a4f3f5
node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65
|
|
os-dev-report |
席位核对 · ACCEPT · 2026-10-23T21:27Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25467
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Cut 3 of the
protocol.tssplit (#24586 ruling A, recorded in6270097840). Cut 1 landed as #25494 and cut 2 as #25518. The claim is6373234211; the seat agreed the scope and its conditions before it. Nineteen PRIVATEObjectStackProtocolImplementationmethods, the query validation, move unedited into a new module,packages/metadata-protocol/src/protocol-query-validation.ts: the section:12797–:14502on baseb2411c001d(1,706 lines, 382 of them from the last 30 days).It is move-only, and the card stays open for the later cuts. Branch:
claude/project-thread-uasfag, the harness-pinned branch for this session, restarted fromorigin/mainatb2411c001dafter cut 2 merged and rebased ontof38832be9c(which touches nothing in this package). It names no issue, so the claim comment is the identity record.Serial with the file's in-flight PRs. At claim, none of the 9 open PRs touched
protocol.ts(each PR's diff against its merge base read; the Version Packages PR #24876 touches onlyCHANGELOG.mdandpackage.jsonin this package). Re-measured at 20:54Z, just before this PR was opened, on all 12 open PRs: one new PR, #25533 (opened 20:29Z), editsprotocol.tsin two places, a docblock line inpublishDrafts' request type (:26260) and one spread in its build-probe call (:27144). Neither is in or next to the moved region (the query-validation section:12797–:14502and the module-level declarations at:392–:5348), andgit merge-treeof this head with #25533's head is clean, so this cut does not yield to it. No open PR touchessecond-object-read-exposure.pin.test.ts.origin/main(74496c64b0) has not touched either file since this branch's base, andgit merge-treewith it is clean. Whichever of the two lands second changesprotocol.ts's byte offsets under the other (see the series note at the end).What moves
The 19 methods, all
private(so the published.d.tsprints no type for any of them, and none is on the series' skip list of public members with inferred return types):assertObjectRegistered,resolveQueryFields,assertQueryParamsAreFields,assertFilterFieldsExist,assertSortFieldsExist,assertProjectionFieldsExist,assertExpandTargetsExist,assertSearchFieldsAreSearchable,assertGroupByFieldsExist,assertAggregationFieldsExist,assertNoInternalFieldEvaluated,refuseInternalInFilter,refuseUnservedRelationTarget,refuseInternalInExpand,servedExpand,servesExpansionTarget,orderByFieldNames,groupByFieldNames,refuseInternalNames.The shape (#23050 shape B, as cuts 1–2 apply it)
fn.call(host, …), marked[#25467 cut 3]; a body that reads no member calls its function directly. None of the 19 wasasync, so no stub changes its modifiers.QueryValidationHosttypes every member as the class member itself (ObjectStackProtocolImplementation['m']). Each moved function types itsthisas thePickof the host over exactly the members its body reads (measured by AST), and each stub annotates its literal asThisParameterType<typeof fn>. There is no cast, and no member becomes public or protected.engine, as a getter so each read happens when the body makes it; and the methodsassertFilterFieldsExist,groupByFieldNames,orderByFieldNames,refuseInternalInExpand,refuseInternalInFilter,refuseInternalNames,refuseUnservedRelationTarget,resolveQueryFields,servedExpand,servesExpansionTarget, as late-bound closures ((...a) => this.m(...a)). The recursiverefuseInternal*walks therefore still recurse through the protocol, and a spy or double set on the instance is still the one the body reaches.Module-level declarations, by reader
Each declaration the moved bodies read was checked by an AST walk over the whole of base
protocol.ts: every identifier reference to it outside its own declaration, attributed to the enclosing class member or top-level statement.Move with the bodies, byte-identical and unexported. Each has no reader outside the moved bodies and the other moved declarations (0 outside references each):
nestedRelationRoute:392warnedNoRegistryForDataGate(let, mutable):536assertObjectRegisteredonly: its one read (:12864) and its one write (:12865). The state moves whole; it is not copied, so there is still exactly one flag.AGGREGATION_FUNCTIONS:4489DATE_GRANULARITIES:4490UNMATERIALIZED_SORT_TYPES:4892invalidQueryError:4988suggestQueryParam:5131collectFilterFieldKeys:5217INTERNAL_FIELD_WALK_DEPTH:5321EXPANSION_TARGET_OPERATION:5337RELATION_CONDITION_TARGET_OPERATION:5348Their initialisers read only package imports (
AggregationFunction,DateGranularity) or nothing, so neither module reads the other's bindings while they are first evaluated.Comments around the moved declarations. Each moved declaration carries exactly its own docblock, byte-identical. Two comments sat above them at base and were decided one by one:
:330–:383iscanonicalMetaType's docblock ("The ONE canonical spelling of a metadata type…"), which sits abovenestedRelationRouteonly because [Decision] v18:查询能否直接按关联记录的字段筛选(例:「客户行业 = 科技」的商机) #20802 inserted that function between the docblock andcanonicalMetaType(:399). It documents a function that stays, so it stays inprotocol.ts; withnestedRelationRoutegone it sits directly abovecanonicalMetaTypeagain. OnlynestedRelationRoute's own docblock (:384–:391) moves.:523–:528is a six-line[#5488]tombstone (PUBLISH_DRAFTS_NAMESPACE_REMEDYretired) that is the leading comment ofwarnedNoRegistryForDataGate. It moves with that declaration, unedited. It was measured both ways: left inprotocol.ts, it shifts the file's byte offsets so that the emitted.d.tssplices an unrelatedprotocol.tsdocblock intogetMetaTypes' inferred return type (normalised hashfd5f361b…or9b65c545…depending on where the docblock above sits), the same splice cut 2 measured ongetDiscovery. Moved, the.d.tsmeets the bar (measured in the next section). See "Series note" at the end.Stay in
protocol.tsand gainexportin place, the only edit to each, imported back by the module:collectFilterReadscollectStoredMetadataFilterFieldsinvalidSortErrornormalizeSortNodes,invalidSortDirectionKeyErrorisObjectOfDisabledPackage,objectPackageDisabledErrorreadMetaItemFace,getMetaItemLayeredisPlainRecordstorableEqual,soleMovedFrom,buildStorableViewNodesuggestFieldName,nearMissKey,QUERY_PARAM_NEAR_MISS,ODATA_SPELLING,FILTER_LOGICAL_KEYSCorrection to the claim. The claim grouped all ten as read by code that stays. Measured by the same AST walk, five of them are read only by code that moves, directly or through each other:
suggestFieldName(7 reads in the moved methods, 1 insuggestQueryParam),nearMissKey(suggestQueryParam,suggestFieldName),QUERY_PARAM_NEAR_MISSandODATA_SPELLING(suggestQueryParam),FILTER_LOGICAL_KEYS(collectFilterFieldKeys);editDistance, which stays unexported, is read only bysuggestFieldName. They stay here anyway, exported in place: staying is the direction the series' rule always allows, the six form one near-miss vocabulary interleaved withnormalizeSortNodes(which stays), and three test comments name "protocol.ts's ownFILTER_LOGICAL_KEYS". Moving the six is a pure move with no reader to migrate, left for a later cut if the seat wants it, so this PR's measured layout is not reopened.src/index.tsexports from./protocol.jsby name, so none of the ten is published (measured below).Imports.
protocol.tsdrops the names that lose every reader (noUnusedLocals):objectNotFoundErrorandrelationConditionSitesfrom@objectstack/core,collectInternalWriteResponseFieldsfrom./write-response-internal-fields.js, and ten names from@objectstack/spec/data. The module imports them from the same specifiers.protocol.tsgains one statement, the import of the new module.The published
.d.tsUnder the series bar (ruling
6348891160), measured on baseb2411c001dand on the head. Both were built against the same dependency tree:turbo run build --filter=@objectstack/metadata-protocol...at base (13/13), thenpnpm --filter @objectstack/metadata-protocol buildat head.dist/index.d.tsanddist/index.d.cts: baseaf5c30c9cb32b251f9a1b075e6347f372fe4be2561bee9c97731155b5d0a62a7(both files), heada3e7fd469c1db0353692b3dcdab14820d843f65ac2e9e896cf2e6d63bf9890bc(both files).2419), the discoverycapabilitieskey union:… "search" | "i18n" | "notifications" | "export" | …at base and… "search" | "export" | "i18n" | "notifications" | …at head. That is a union-member order swap, the kind the ruling names. The file size is identical (769,048 bytes).5b98a8e857554959f7561563d6a0742110c5d3d8ad6119065ed5a69b0bc06f95, the same value as at cuts 1 and 2. The normaliser, run frompackages/metadata-protocol:dist/index.d.ts: line 1348,private engine;, made public (engine;), gives raw2ee8f1cbc4b49721d0967bbcddd906868775f4a889fda8d1daffa8ba2804f0a8and normalisedf40390e5c426e0c6e24523cd6908726d3f3cab87f9a4ac36df20992bec04da95. Both differ from the real hashes.import()andrequireof the built bundles). The sorted list hashese47ac7c753bcb8bee1e79c1a5f79f7394cf030bd526de81ea8bcbfcb71fa024aon all four, as at cuts 1 and 2. None of the ten newly exported declarations appears.No changeset:
skip-changeset, per ruling6348891160.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 four leading spaces removed. Each moved declaration has the same tokens and its own docblock byte-identical (nestedRelationRoutecarries one docblock at head where base attached two; the other iscanonicalMetaType's, above). 6,585 tokens compared (6,149 in the methods), 0 differ. Negative control: one changed token makes the check report a difference.protocol.ts: 232 of the 256 base statements other than the class are byte-identical with their leading comments. The other 24 are the 11 moved declarations, the 10 that gainexport, the 2 narrowed import statements, andcanonicalMetaType, whose code is byte-identical and whose leading comment is now its own docblock (above). The one new statement is the import of the new module.dispatch-gatesmeasures it: "under").protocol.tsgoes from 32,309 to 31,223 lines;protocol-query-validation.tshas 1,393.Readers of the moved lines
packages/core/src/security/second-object-read-exposure.pin.test.ts), found by running the cross-package tests that readprotocol.tsby path; not listed in the claim, so disclosed here. It reads source by path: which files call the reference-target arbiter (referenceTargetOf), the body of each door's decision function, and each wiring call site. The arbiter call,servesExpansionTarget,refuseUnservedRelationTargetandservedExpandnow live in the new module, so at head without a migration it fails 3 of 4 tests (an unclassified caller, a decision function that no longer asks the decision, and a classified file the matcher no longer fires on). Six path strings move toprotocol-query-validation.ts: the classified caller key, bothdecision.files, the executes row'sdecidedIn, the relation row'swiring.file(its one call site,this.refuseUnservedRelationTarget(, moved; the match left inprotocol.tswould only be the stub's host closure), and the vacuity control'sservedExpandbody read. The$expandrow's wiring stays onprotocol.ts, wherefindData/getDatastill callthis.servedExpand(. No row is added, removed or reworded; 4/4 pass at head, as at base.protocol.bracketed-refusal-opener-absence.test.ts). 17new Error(sites move: 16 in the methods and 1 ininvalidQueryError. Measured at both ends:protocol.ts104 → 87, against its floor of 5. The new module is added toPRODUCERSwith its measured count as its floor,'protocol-query-validation.ts': 17, as cut 1 listedprotocol-batch-data.ts. With the exposure pin's paths above, those are the only edits outside the move; this one adds exactly one test (the newit.eachrow), which passes. No moved site opens with a bracketed tag.protocol.tsentry inscripts/adr-anchors/describes these bodies, read paragraph by paragraph for half-matches too: they cover ADR-0029 D9.8/D9.9 hydration seams, ADR-0119batchData, ADR-0005/0087 view storage and ADR-0126 flow Regime C. So no entry is added.[#5488]tombstone that moves withwarnedNoRegistryForDataGate(above).check:adr-anchors: 82 → 82 anchored files; decision-letter citations on the anchored surface 1,969 → 1,968, measured at both ends.docs/qa/platform-checklist/areas/api-backend.json(:514,:515,:2007,:2855) andsearch.json(:202) anchorprotocol.ts#moved methods. The method shells stay, so they still resolve; they are not edited, as cuts 1 and 2 left theirs.check-platform-checklistexits 0 at both ends with identical output.isSystemread (check-system-context-census: 149 sites at both ends, identical output), no enginefind/findOne/count/aggregatecall (soscripts/query-options-erasure-baseline.json'sprotocol.ts: 6is unaffected), no engine write (check-tenant-audit-census: 238 sites at both ends, identical output, no--write), no engine-double contract seam, and nolookupArtifactItem(call (the call-site pin's enumeration is unaffected)../protocol.ts(register-item,lookup-artifact-itemandview-expansion-reconcilecall-site pins,hydrate-overlay-canonical-type,object-registry-write-through-spelling,query-transport-dialect,read-verb-canonical-fold, the two recovery-door pins) all pass at head; none of their patterns occurs in the moved text.Verification
tsc --noEmit(package) exit 0;eslinton the four changed files exit 0.packages/metadata-protocolsuite.PRODUCERSrow,protocol-query-validation.ts raises no message opening with a bracketed lowercase tag(passed).protocol.tsby path, run at head, all pass:corerow-serving-door-exposure.pinandsecond-object-read-exposure.pin(7, the latter migrated above)objectqlcore-boundary.ratchet(2)specmetadata-type-registry-allow-org-override-absenceandscripts/liveness/evidence(44)clientandrestdiscovery-double-retired-key(3 + 2)speccheck-livenessexits 0.protocol.tsnext to a moved symbol are prose (comments,docs/qatext, a dogfood ledger'senforcementstring), read by no code.check-adr-symbol-anchors(2,384 anchors resolve at both, identical output),check-adr-anchors,check-tenant-audit-census,check-system-context-census,check-platform-checklist.dispatch-gates --commands --changedderives 60 gates on the final tree (4 changed paths); all 60 exit 0 and--ranreconciles 60 of 60 with 0 NOT-MEASURED, each run at head with its exit code.check-adr-symbol-anchorsandcheck-livenessare not derived and were run by hand (above). The full list and the--ranreconcile are in the report on this PR.Series note: the inferred-return
.d.tsdepends onprotocol.tsbyte offsetsMeasured on this cut: with the same moved code, four placements of the two comments above give three different
.d.tsfiles. The emitter prints the inferred return types of the public members listed below (heregetMetaTypes) with nodes that keep their original positions, and it reads leading comments for them out ofprotocol.tsat those positions. So whether aprotocol.tsdocblock is spliced into the published type depends on where the file's bytes fall, not on what moved. Any edit toprotocol.tscan trip it, a cut or not. Each cut in this series measures it (this PR meets the bar, measured above), and the carry-forward stands.Carry-forward (unchanged from cut 2)
A public member whose return type is inferred stays in
protocol.tsin this series; annotating its type would be a signature edit and needs a ruling. Later cuts skipgetDiscovery,getMetaTypes,getMetaItems,getMetaItemsForExecution,getMetaItem,getMetaItemForExecution,getUiView,findData,getData,validateData,createData,cloneData,updateData,deleteData,saveMetaItem.🤖 Generated with Claude Code
https://claude.ai/code/session_01J8pUyVs5eas1AjMdReJM7C