Skip to content

refactor(metadata-protocol): move the query validation out of protocol.ts into protocol-query-validation.ts (#25467 cut 3) - #25538

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-uasfag
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-uasfag

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25467
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840

Cut 3 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cut 1 landed as #25494 and cut 2 as #25518. The claim is 6373234211; the seat agreed the scope and its conditions before it. Nineteen PRIVATE ObjectStackProtocolImplementation methods, the query validation, move unedited into a new module, packages/metadata-protocol/src/protocol-query-validation.ts: the section :12797–:14502 on base b2411c001d (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 from origin/main at b2411c001d after cut 2 merged and rebased onto f38832be9c (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 only CHANGELOG.md and package.json in 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), edits protocol.ts in two places, a docblock line in publishDrafts' 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–:14502 and the module-level declarations at :392–:5348), and git merge-tree of this head with #25533's head is clean, so this cut does not yield to it. No open PR touches second-object-read-exposure.pin.test.ts. origin/main (74496c64b0) has not touched either file since this branch's base, and git merge-tree with it is clean. Whichever of the two lands second changes protocol.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.ts prints 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)

  • The class keeps every method with its name, docblock, signature and position. Each body is now one host literal plus fn.call(host, …), marked [#25467 cut 3]; a body that reads no member calls its function directly. None of the 19 was async, so no stub changes its modifiers.
  • QueryValidationHost types every member as the class member itself (ObjectStackProtocolImplementation['m']). Each moved function types its this as the Pick of the host over exactly the members its body reads (measured by AST), and each stub annotates its literal as ThisParameterType<typeof fn>. There is no cast, and no member becomes public or protected.
  • The members read, measured by AST: the field engine, as a getter so each read happens when the body makes it; and the methods assertFilterFieldsExist, groupByFieldNames, orderByFieldNames, refuseInternalInExpand, refuseInternalInFilter, refuseInternalNames, refuseUnservedRelationTarget, resolveQueryFields, servedExpand, servesExpansionTarget, as late-bound closures ((...a) => this.m(...a)). The recursive refuseInternal* 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):

Declaration Base line Readers
nestedRelationRoute :392 moved bodies only
warnedNoRegistryForDataGate (let, mutable) :536 assertObjectRegistered only: 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 :4489 moved bodies only
DATE_GRANULARITIES :4490 moved bodies only
UNMATERIALIZED_SORT_TYPES :4892 moved bodies only
invalidQueryError :4988 moved bodies only
suggestQueryParam :5131 moved bodies only
collectFilterFieldKeys :5217 moved bodies only
INTERNAL_FIELD_WALK_DEPTH :5321 moved bodies only
EXPANSION_TARGET_OPERATION :5337 moved bodies only
RELATION_CONDITION_TARGET_OPERATION :5348 moved bodies only

Their 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:

  • Base :330–:383 is canonicalMetaType's docblock ("The ONE canonical spelling of a metadata type…"), which sits above nestedRelationRoute only because [Decision] v18:查询能否直接按关联记录的字段筛选(例:「客户行业 = 科技」的商机) #20802 inserted that function between the docblock and canonicalMetaType (:399). It documents a function that stays, so it stays in protocol.ts; with nestedRelationRoute gone it sits directly above canonicalMetaType again. Only nestedRelationRoute's own docblock (:384–:391) moves.
  • Base :523–:528 is a six-line [#5488] tombstone (PUBLISH_DRAFTS_NAMESPACE_REMEDY retired) that is the leading comment of warnedNoRegistryForDataGate. It moves with that declaration, unedited. It was measured both ways: left in protocol.ts, it shifts the file's byte offsets so that the emitted .d.ts splices an unrelated protocol.ts docblock into getMetaTypes' inferred return type (normalised hash fd5f361b… or 9b65c545… depending on where the docblock above sits), the same splice cut 2 measured on getDiscovery. Moved, the .d.ts meets the bar (measured in the next section). See "Series note" at the end.

Stay in protocol.ts and gain export in place, the only edit to each, imported back by the module:

Declaration Read by code that stays
collectFilterReads collectStoredMetadataFilterFields
invalidSortError normalizeSortNodes, invalidSortDirectionKeyError
isObjectOfDisabledPackage, objectPackageDisabledError readMetaItemFace, getMetaItemLayered
isPlainRecord storableEqual, soleMovedFrom, buildStorableViewNode
suggestFieldName, nearMissKey, QUERY_PARAM_NEAR_MISS, ODATA_SPELLING, FILTER_LOGICAL_KEYS none: see below

Correction 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 in suggestQueryParam), nearMissKey (suggestQueryParam, suggestFieldName), QUERY_PARAM_NEAR_MISS and ODATA_SPELLING (suggestQueryParam), FILTER_LOGICAL_KEYS (collectFilterFieldKeys); editDistance, which stays unexported, is read only by suggestFieldName. They stay here anyway, exported in place: staying is the direction the series' rule always allows, the six form one near-miss vocabulary interleaved with normalizeSortNodes (which stays), and three test comments name "protocol.ts's own FILTER_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.ts exports from ./protocol.js by name, so none of the ten is published (measured below).

Imports. protocol.ts drops the names that lose every reader (noUnusedLocals): objectNotFoundError and relationConditionSites from @objectstack/core, collectInternalWriteResponseFields from ./write-response-internal-fields.js, and ten names from @objectstack/spec/data. The module imports them from the same specifiers. protocol.ts gains one statement, the import of the new module.

The published .d.ts

Under the series bar (ruling 6348891160), measured on base b2411c001d and on the head. Both were built against the same dependency tree: turbo run build --filter=@objectstack/metadata-protocol... at base (13/13), then pnpm --filter @objectstack/metadata-protocol build at head.

  • Raw sha256 of dist/index.d.ts and dist/index.d.cts: base af5c30c9cb32b251f9a1b075e6347f372fe4be2561bee9c97731155b5d0a62a7 (both files), head a3e7fd469c1db0353692b3dcdab14820d843f65ac2e9e896cf2e6d63bf9890bc (both files).
  • The raw diff is one swapped line (2419), the discovery capabilities key 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).
  • Normalised sha256, base and head (both files): 5b98a8e857554959f7561563d6a0742110c5d3d8ad6119065ed5a69b0bc06f95, the same value as at cuts 1 and 2. The normaliser, run from packages/metadata-protocol:
node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum
  • Positive control on a copy of the head dist/index.d.ts: line 1348, private engine;, made public (engine;), gives raw 2ee8f1cbc4b49721d0967bbcddd906868775f4a889fda8d1daffa8ba2804f0a8 and normalised f40390e5c426e0c6e24523cd6908726d3f3cab87f9a4ac36df20992bec04da95. Both differ from the real hashes.
  • Export tables: the ESM and CJS bundles each export the same 78 names at base and head, loaded at runtime (import() and require of the built bundles). The sorted list hashes e47ac7c753bcb8bee1e79c1a5f79f7394cf030bd526de81ea8bcbfcb71fa024a on all four, as at cuts 1 and 2. None of the ten newly exported declarations appears.

No changeset: skip-changeset, per ruling 6348891160.

Proof of move-only

  • Bodies and declarations, by AST token (recursive getChildren leaves): each moved function's parameters (less the added this), 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 (nestedRelationRoute carries one docblock at head where base attached two; the other is canonicalMetaType's, above). 6,585 tokens compared (6,149 in the methods), 0 differ. Negative control: one changed token makes the check report a difference.
  • Class side: the class has 298 members at base and head. The 279 that did not move are byte-identical to base. Each of the 19 stubs keeps its docblock and signature byte-identical.
  • Top level of 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 gain export, the 2 narrowed import statements, and canonicalMetaType, 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.
  • Size: +1,544 / −1,235 over 4 files, 2,779 changed lines, under the 3,000-line human-merge bar (dispatch-gates measures it: "under"). protocol.ts goes from 32,309 to 31,223 lines; protocol-query-validation.ts has 1,393.

Readers of the moved lines

  • Second-object read exposure pin (packages/core/src/security/second-object-read-exposure.pin.test.ts), found by running the cross-package tests that read protocol.ts by 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, refuseUnservedRelationTarget and servedExpand now 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 to protocol-query-validation.ts: the classified caller key, both decision.files, the executes row's decidedIn, the relation row's wiring.file (its one call site, this.refuseUnservedRelationTarget(, moved; the match left in protocol.ts would only be the stub's host closure), and the vacuity control's servedExpand body read. The $expand row's wiring stays on protocol.ts, where findData/getData still call this.servedExpand(. No row is added, removed or reworded; 4/4 pass at head, as at base.
  • Bracketed-refusal pin (protocol.bracketed-refusal-opener-absence.test.ts). 17 new Error( sites move: 16 in the methods and 1 in invalidQueryError. Measured at both ends: protocol.ts 104 → 87, against its floor of 5. The new module is added to PRODUCERS with its measured count as its floor, 'protocol-query-validation.ts': 17, as cut 1 listed protocol-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 new it.each row), which passes. No moved site opens with a bracketed tag.
  • ADR anchors. No paragraph of the protocol.ts entry in scripts/adr-anchors/ describes these bodies, read paragraph by paragraph for half-matches too: they cover ADR-0029 D9.8/D9.9 hydration seams, ADR-0119 batchData, ADR-0005/0087 view storage and ADR-0126 flow Regime C. So no entry is added.
    • One decision letter in the moved text leaves the anchored surface: ADR-0121 D2, in the [#5488] tombstone that moves with warnedNoRegistryForDataGate (above).
    • check:adr-anchors: 82 → 82 anchored files; decision-letter citations on the anchored surface 1,969 → 1,968, measured at both ends.
  • Platform checklist. docs/qa/platform-checklist/areas/api-backend.json (:514, :515, :2007, :2855) and search.json (:202) anchor protocol.ts# moved methods. The method shells stay, so they still resolve; they are not edited, as cuts 1 and 2 left theirs. check-platform-checklist exits 0 at both ends with identical output.
  • Censuses and ledgers. The moved text has no isSystem read (check-system-context-census: 149 sites at both ends, identical output), no engine find/findOne/count/aggregate call (so scripts/query-options-erasure-baseline.json's protocol.ts: 6 is unaffected), no engine write (check-tenant-audit-census: 238 sites at both ends, identical output, no --write), no engine-double contract seam, and no lookupArtifactItem( call (the call-site pin's enumeration is unaffected).
  • Source scans. The package's source scans that read ./protocol.ts (register-item, lookup-artifact-item and view-expansion-reconcile call-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; eslint on the four changed files exit 0.
  • packages/metadata-protocol suite.
    • Base: 21,476 tests (21,457 passed, 0 failed, 19 skipped), 264 files.
    • Head: 21,477 (21,458 passed, 0 failed, 19 skipped), 264 files.
    • Compared per test name and status: none lost, none changed status, one added: the new PRODUCERS row, protocol-query-validation.ts raises no message opening with a bracketed lowercase tag (passed).
  • Tests outside the package that read protocol.ts by path, run at head, all pass:
    • core row-serving-door-exposure.pin and second-object-read-exposure.pin (7, the latter migrated above)
    • objectql core-boundary.ratchet (2)
    • spec metadata-type-registry-allow-org-override-absence and scripts/liveness/evidence (44)
    • client and rest discovery-double-retired-key (3 + 2)
    • spec check-liveness exits 0.
    • The other files that name protocol.ts next to a moved symbol are prose (comments, docs/qa text, a dogfood ledger's enforcement string), read by no code.
  • Hand gates at base and head, all exit 0 at both: 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.
  • Derived gates. dispatch-gates --commands --changed derives 60 gates on the final tree (4 changed paths); all 60 exit 0 and --ran reconciles 60 of 60 with 0 NOT-MEASURED, each run at head with its exit code. check-adr-symbol-anchors and check-liveness are not derived and were run by hand (above). The full list and the --ran reconcile are in the report on this PR.

Series note: the inferred-return .d.ts depends on protocol.ts byte offsets

Measured on this cut: with the same moved code, four placements of the two comments above give three different .d.ts files. The emitter prints the inferred return types of the public members listed below (here getMetaTypes) with nodes that keep their original positions, and it reads leading comments for them out of protocol.ts at those positions. So whether a protocol.ts docblock is spliced into the published type depends on where the file's bytes fall, not on what moved. Any edit to protocol.ts can 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.ts in this series; annotating its type would be a signature edit and needs a ruling. Later cuts skip getDiscovery, getMetaTypes, getMetaItems, getMetaItemsForExecution, getMetaItem, getMetaItemForExecution, getUiView, findData, getData, validateData, createData, cloneData, updateData, deleteData, saveMetaItem.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8pUyVs5eas1AjMdReJM7C

…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>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 20:56
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 62 documentable anchor(s).

39 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 38 pages), /data/{target} (route, 57 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f87625cf80196d64e9926c1e45fbf01d93acd226 — the merge of head 3b1ae504137ea60dfb810bd4d25c68eb57a4f3f5 into base 74496c64b0637103f87cceec3f37932a48813e65, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 74496c64b0637103f87cceec3f37932a48813e65 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

os-dev-report
{
"issue": 25467,
"status": "done",
"branch": "claude/project-thread-uasfag",
"pr": "#25538",
"session": "session_01J8pUyVs5eas1AjMdReJM7C — this run's harness-stamped id, on the Claude-Session: trailer of the commit on the branch",
"premise_still_valid": true,
"summary": "Cut 3 of the protocol.ts split (#24586 ruling A, 6270097840; d.ts bar 6348891160). Claim 6373234211, scope agreed with the seat before it. The 19 private query-validation methods (assertObjectRegistered, resolveQueryFields, assertQueryParamsAreFields, the assertFieldsExist family, assertSearchFieldsAreSearchable, assertNoInternalFieldEvaluated, refuseInternalInFilter, refuseUnservedRelationTarget, refuseInternalInExpand, servedExpand, servesExpansionTarget, orderByFieldNames, groupByFieldNames, refuseInternalNames) moved unedited to packages/metadata-protocol/src/protocol-query-validation.ts (host QueryValidationHost: engine as a getter, ten methods as late-bound closures). 11 module declarations read only by the moved code moved with them, byte-identical, each with 0 outside references by AST walk; warnedNoRegistryForDataGate (the mutable let) has its one read and one write in assertObjectRegistered and moved whole, not copied. 10 declarations stay with export added in place (index.ts exports protocol.js by name, so none is published). Three things differ from the claim and are disclosed in the PR body: (1) canonicalMetaType's 54-line docblock, which sat above nestedRelationRoute at base, stays in protocol.ts; a six-line [#5488] tombstone moves with warnedNoRegistryForDataGate because leaving it in protocol.ts made the d.ts splice an unrelated docblock into getMetaTypes' inferred type (byte-offset dependence, measured on four placements; series note in the PR body); (2) a path reader the claim did not list, packages/core/src/security/second-object-read-exposure.pin.test.ts, had six path strings migrated to the new module (it fails 3 of 4 at head without them); (3) five of the ten export-in-place declarations (suggestFieldName, nearMissKey, QUERY_PARAM_NEAR_MISS, ODATA_SPELLING, FILTER_LOGICAL_KEYS; also editDistance, unexported) are read only by moved code, not by code that stays as the claim grouped them; they stay, which the rule allows, and moving them is left for the seat. Serial rule: at claim none of 9 open PRs touched protocol.ts; at 20:54Z, of 12, #25533 (opened 20:29Z) edits two lines in publishDrafts, disjoint from the moved region, git merge-tree clean with this head, so the cut did not yield. No open question.",
"tests": "pnpm --filter @objectstack/metadata-protocol vitest run (JSON reporter) under os-verify-lock. Base b2411c0: 21,476 tests (21,457 passed, 0 failed, 19 skipped), 264 files. Head 3b1ae50: 21,477 (21,458 passed, 0 failed, 19 skipped), 264 files. Compared per test name and status: 0 lost, 0 changed, 1 added (the new PRODUCERS it.each row, protocol-query-validation.ts, passed). Move-only, so no ablation. Package tsc --noEmit exit 0; eslint on the 4 changed files exit 0. Out-of-package tests that read protocol.ts by path, at head, all pass: core row-serving-door-exposure.pin and second-object-read-exposure.pin (7; the latter migrated, 4/4 at base and head), objectql core-boundary.ratchet (2), spec metadata-type-registry-allow-org-override-absence and scripts/liveness/evidence (44), client and rest discovery-double-retired-key (3 + 2); spec check-liveness exit 0. Move proof: each moved function's parameters (less the added this), return type and body have the same AST leaf-token sequence as the base method, each body line is the base line minus four leading spaces; each moved declaration has the same tokens and its own docblock byte-identical; 6,585 tokens compared, 0 differ. Negative control: one changed token fails the check. Class: 298 members both ends, 279 byte-identical, 19 stubs keep docblock and signature byte-identical. Top level: 232 of 256 base statements byte-identical with leading comments; the other 24 are 11 moved, 10 export added, 2 narrowed imports, and canonicalMetaType (code byte-identical, leading comment now its own docblock). +1,544 / -1,235 over 4 files (2,779, under 3,000); protocol.ts 32,309 to 31,223 lines, the module 1,393.",
"dts_block": {
"files": "packages/metadata-protocol dist/index.d.ts and dist/index.d.cts; base b2411c0 built with turbo run build --filter=@objectstack/metadata-protocol... (13/13), head built with pnpm --filter @objectstack/metadata-protocol build against the same dependency tree; re-built on the final head after the last comment edit",
"raw_sha256": "base index.d.ts and index.d.cts af5c30c9cb32b251f9a1b075e6347f372fe4be2561bee9c97731155b5d0a62a7 · head index.d.ts and index.d.cts a3e7fd469c1db0353692b3dcdab14820d843f65ac2e9e896cf2e6d63bf9890bc",
"normalised_sha256": "base 5b98a8e857554959f7561563d6a0742110c5d3d8ad6119065ed5a69b0bc06f95 · head 5b98a8e857554959f7561563d6a0742110c5d3d8ad6119065ed5a69b0bc06f95 (both files, both sides)",
"normaliser": "node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]
"(?:\s*\|\s*"[^"\n]")+/g,r=>r.split(/\s\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum",
"raw_diff_lines": 4,
"raw_diff": "one swapped line, line 2419: the discovery capabilities key union, ... "search" | "i18n" | "notifications" | "export" ... at base, ... "search" | "export" | "i18n" | "notifications" ... at head; a union-member order swap, the kind ruling 6348891160 names. File size identical (769,048 bytes). The normalised diff is empty.",
"positive_control": "head index.d.ts line 1348, private engine; made public (engine;): raw 2ee8f1cbc4b49721d0967bbcddd906868775f4a889fda8d1daffa8ba2804f0a8, normalised f40390e5c426e0c6e24523cd6908726d3f3cab87f9a4ac36df20992bec04da95, both different from the real hashes.",
"exports": "ESM and CJS bundles loaded at runtime: 78 names each at base and head; sorted-list sha256 e47ac7c753bcb8bee1e79c1a5f79f7394cf030bd526de81ea8bcbfcb71fa024a on all four, as at cuts 1 and 2; none of the ten newly exported declarations appears.",
"note": "No changeset (skip-changeset), per 6348891160. Layouts measured and not shipped: tombstone left in protocol.ts with canonicalMetaType's docblock restored, normalised 9b65c5452f40ab54f43b6c7e00c82f923a88d3dd4d480dc5ef143dfd49c56b3b; tombstone left with the docblock moved, normalised fd5f361b...; each splices an unrelated protocol.ts docblock into getMetaTypes' inferred return type."
},
"adr_anchor_block": {
"gate": "node scripts/check-adr-symbol-anchors.mjs at base b2411c0 and final head, exit 0 at both with identical output: 2,384 anchors across 142 records resolve.",
"adr_anchors": "pnpm check:adr-anchors exit 0 at base and head; anchored files 82 to 82; decision-letter citations on the anchored surface 1,969 to 1,968, measured at both ends. The one that leaves is ADR-0121 D2, in the [#5488] tombstone that moves with warnedNoRegistryForDataGate. No entry added: no paragraph of the protocol.ts entry (ADR-0029 D9.8/D9.9, ADR-0119 batchData, ADR-0005/0087 view storage, ADR-0126 flow Regime C) describes the moved bodies, re-read for half-paragraph matches."
},
"gates": "Local on the final head 3b1ae50: dispatch-gates --commands --changed derives 60 commands (4 changed paths), each run with its exit captured; all 60 exit 0. --ran: 60 derived, 60 run, 0 NOT-MEASURED (a derived zero). Run by hand, not derived, at base and head, all exit 0 at both: check-adr-symbol-anchors (identical output), check:adr-anchors (letters above), check-tenant-audit-census (238 write sites, identical output, no --write), check-system-context-census (149 elevation read sites, identical output), check-platform-checklist (identical output; api-backend.json :514, :515, :2007, :2855 and search.json :202 anchor protocol.ts# moved methods and resolve on the shells, read and not edited). Bracketed-refusal PRODUCERS: new Error( in protocol.ts 104 to 87 (floor 5), new module row 'protocol-query-validation.ts': 17 = its measured count. No engine find/findOne/count/aggregate in the moved text (erasure baseline protocol.ts 6 unaffected), no engine write or engine-double seam, no isSystem read, no lookupArtifactItem( call. Merge check: origin/main 74496c6 has not touched packages/metadata-protocol or the core pin since the branch base; git merge-tree clean; PR mergeable_state clean. CI on head 3b1ae50: 46 check runs, 34 success, 12 skipped by their own filters, 0 failed or pending. Success: Auto Label, Build Core, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, filter, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Test Core, Test Core (1/6) to (6/6), The card this PR closes must claim this branch, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check. Skipped: 18.x upgrade harness (opt-in) x2, Auto Label (second run), Build Docs, Check Changeset (second run), Check PR Size (second run), Console Pin Gate, the sharded Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke (opt-in) x2, Temporal Conformance (live PG + MySQL).",
"docs_drift": "Advisory. Re-derived with node scripts/docs-audit/affected-docs.mjs --json 74496c6 on the head: 50 pages (39 hand-written, 11 release-owned) from 62 anchors, the same counts as the bot comment 6374575615. The pages are reached through literals inside the moved bodies and names the move relocated: INVALID_FIELD (19 pages), updated_at (19), groupBy (17), /data/{target}/{id} (12), searchableFields (9), /data/:object/query (7), INVALID_QUERY (5), and fewer. Every method keeps its name, signature, docblock and behaviour and every literal moved unedited, so no page goes stale. No page reads the moved lines by path.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "No MCP write tool. GitHub reads through gh api GET and the GitHub MCP list_pull_requests read (open PRs for the serial checks), PR state, check runs. Also claude-code-remote subscribe_pr_activity on #25538 (a session subscription, not a GitHub write).",
"api_writes": "5 strokes, all through the scripts/pm relay or git: (1) post-stamped claim comment 6373234211 on #25467 (relay run 40013035600); (2) git push of claude/project-thread-uasfag at 3b1ae50 (new branch on f38832b; the merged cut 2 branch had been deleted by the queue); (3) fleet-write pr_create, PR #25538 draft with assignee os-sales and label skip-changeset (relay run 40022884493; body read back identical, 19,160 bytes); (4) fleet-write pr_request_reviewers os-sales (relay run 40022955108); (5) this report, via post-stamped.",
"open_questions": [],
"out_of_scope_findings": [
"The published d.ts of the 15 public members with inferred return types depends on protocol.ts byte offsets: with the same moved code, four placements of two comments gave three different d.ts files (an unrelated protocol.ts docblock spliced into getMetaTypes' type in two). Any protocol.ts edit, not only a cut, can trip it; #25533 (open, edits protocol.ts) and this PR shift each other's offsets, so whichever lands second may change the published d.ts. Recorded as a series note in the PR body; not acted on.",
"Six declarations still in protocol.ts are read only by the moved code (suggestFieldName, nearMissKey, editDistance, QUERY_PARAM_NEAR_MISS, ODATA_SPELLING, FILTER_LOGICAL_KEYS); a later cut can move them with no reader to migrate. Left for the seat."
]
}

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T21:27Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25467 cut 3 (claim 6373234211; os-dev-report 6375009985) · reviewed head 3b1ae50413 · merge base f38832be9c

  • Shape: the query validation (11 declarations) moves unedited into protocol-query-validation.ts with the cut 1 host pattern. 4 files, +1,544 / −1,235, 2,779 changed lines, under the 3,000 bar.
  • Departures, all disclosed in the PR body and accepted:
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree:
    • turbo run build --filter=@objectstack/metadata-protocol at f38832be9c and at this head.
    • Raw goes from af5c30c9cb32b251… to a3e7fd469c1db035… on both index.d.ts and index.d.cts. The raw diff is one line, line 2419: the capabilities key union is emitted in a different order (export moves). No member is added or removed.
    • Normalised: 5b98a8e857554959… at both ends, so the cut meets the sorted-union bar.
    • The seat's positive control makes head line 1348, private engine;, public. That moves both hashes, to raw 2ee8f1cbc4b49721… and normalised f40390e5c426e0c6….
    • CJS exports: 78 at both ends.
  • Gates re-run by the seat, all exit 0 at base and at head: check-adr-symbol-anchors (2,384 anchors at both ends), check-tenant-audit-census (238 sites), check-system-context-census (149 sites), check-adr-anchors, check-dispatcher-error-vocabulary, check-platform-checklist.
  • Decision-letter count, read by the seat: 1969 at the merge base and 1968 at head (ADR-0121 D2 leaves with the body). main has not touched packages/metadata-protocol since the merge base, so the merged tree reads 1968 too.
  • PRODUCERS: the seat counts new Error( at 104 in protocol.ts at base, and at 87 plus 17 in the new module at head. The new protocol-query-validation.ts: 17 floor row matches.
  • Tests and gates (report): 60 of 60 derived gates exit 0, with none NOT-MEASURED.
  • CI: 34 success and 12 expected skips, none failed or pending (46 runs).
  • Mergeable: git merge-tree onto today's main is clean. It is also clean pairwise against fix(metadata-protocol): publish-drafts widget probes run as the publishing caller #25533, which edits publishDrafts in protocol.ts. Whichever lands second shifts the other's byte offsets, so the seat re-reads that one's .d.ts before it lands.
  • Body: the attribution header and the os-sales reviewer are in place. The footers are present.
  • Landing: not governed (check-governed-merges --pr 25538: 2,779 lines). Ready + auto-merge through the relay. The card stays open and dispatched. The next cut is claimed afresh on the merged main.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 21:28
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 21:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 798d480 Oct 23, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-uasfag branch October 23, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants