Skip to content

refactor(metadata-protocol): move the object-extender fold out of protocol.ts into protocol-object-extender-fold.ts (#25467 cut 30) - #27129

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ybjm3h
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ybjm3h

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

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

Cut 30 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cuts 1–29 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404, #26449, #26654, #26704, #26807, #26873, #26933, #26980 and #27029. The claim is 6460856054. It takes the cut 30 proposal of the cut 29 report (6458845738): one member on its own theme. It passed the AST screen and the read-seam census and moved unedited.

One ObjectStackProtocolImplementation member moves unedited into a new module, packages/metadata-protocol/src/protocol-object-extender-fold.ts (#23050 shape B): foldObjectExtendersFromRegistry. It folds the registry's extend contributors onto an OBJECT body that came from the MetadataService, so that the by-name read serves the object's resolved schema as the list read does (#7556).

protocol.ts loses 10 net lines (29,425 to 29,415). The cut is move-only, and the card stays open for later cuts.

Branch claude/project-thread-ybjm3h is the harness-pinned branch for this thread. It was restarted from main cbb3a1a1a5 (after cut 29's merge). It names no issue, so the claim comment is the identity record. It holds the move commit b542764e48 and three merges of main:

The claim asked for one merge. The extra two follow the derivation's STALE TREE findings. The merged tree was measured against main itself (see "The merged tree against main").

A deviation, disclosed: the cut 25 comment-offset trap fired, and the cut's own comment was shortened

The first layout of this cut (move commit cc3ec04204, never pushed) broke raw and sorted-union .d.ts identity:

  • raw went to 701056df78aed858…, 788,085 bytes (+4,967);
  • normalised went to 522aaecdadbe5b51….

The only hunk was the one cut 25 met. tsc's declaration emitter reused an inferred @objectstack/spec node (ast?: unknown) and printed comment ranges read from protocol.ts at that node's offset. That pasted the "Zod schemas used to validate overlay items" docblock into the type.

Printed with comments removed (the TS printer's removeComments), base and that head were identical (1583192e38f78f85). It was still a published .d.ts change, so it failed the ruling 6348891160 bar. As the claim's carried note directs, the fix was the cut's own added text, never the bar.

The import comment the cut adds was reworded 21 characters shorter, and the move commit was amended to b542764e48. Raw identity came back. Every head reading below is from b542764e48, including the tsc, hand-gate, positive-control and suite legs, which were re-run on it.

Serial with the file's in-flight PRs

The claim found no open PR editing protocol.ts and asked for a re-check before this PR was opened.

main moved 5 commits after the third merge (to 07d18e6927). Their git merge-tree with this head is clean, and none touches this package. The derivation on 307e5c9975 still names one stale file, .github/workflows/lint.yml. So the gates were derived again on that merge-tree (this head with 07d18e6927): it reads no stale finding and the same 46 gate commands, line for line. main was not merged a fourth time. The PR's merge ref runs CI against current main.

The order's screen (private statics, new Error(, the read-seam census)

The member passed:

  • No private-static read. The AST walk found no access on the ObjectStackProtocolImplementation identifier and none on this.constructor.
  • No error constructed. The body has no new Error(, so no PRODUCERS row is owed.
  • Read-seam census. check-durability-degradation-log-level --list was taken on the base and again on the cut. Both read 89 read seam(s), the claim's base, and the lists are identical once [@:][0-9]+ is stripped. The full readings are under "The durability --list".

What moves

Member Base line Visibility Body lines AST tokens (params + return + body) Reads
foldObjectExtendersFromRegistry :8901 private 18 (with its comments) 112 engine (field); module-level PLURAL_TO_SINGULAR

protocol-object-extender-fold.ts has 68 lines.

The shape (#23050 shape B, as cuts 1–29 apply it)

  • The class keeps the member. It keeps its name, docblock, signature and position. Its body now delegates, marked [#25467 cut 30].
  • ObjectExtenderFoldHost types the one member the body reads as the class member itself (ObjectStackProtocolImplementation['engine']).
  • The engine field is handed over as a getter, so the read finds it at call time, as before.
  • The try/catch around the in-memory registry.foldObjectExtendersOnto call moves with the body, unedited, as the claim says.
  • No member changes visibility.
  • Every caller still calls the class method. There are 6, at :7412, :9613, :11092, :11144, :11493 and :11723 (base). None changes.

Module-level declarations, by reader: no import leaves protocol.ts

The one module-level name the body reads is PLURAL_TO_SINGULAR, from @objectstack/spec/shared. It has many other readers in protocol.ts and stays. The new module imports it from there too. So the top level of protocol.ts changes by the new module's import alone.

The published .d.ts

The series bar is ruling 6348891160: sorted-union identity, measured first, with raw identity reported when it holds. Both hold for this cut at b542764e48, and on the merged tree against main. See the deviation above for the first layout.

Base cbb3a1a1a5 and head b542764e48 were built with pnpm --filter @objectstack/metadata-protocol build against one dependency tree. The closure was built at head by a full pnpm build (116/116). For the base build, the package's src/ was swapped tree-only to base (the new module removed) and restored from HEAD under a trap. Afterwards git diff HEAD was empty, git status was clean, and both touched blobs equalled HEAD's.

  • Normalised sha256 (the ruling's bar): cd4b7becaeffffd99b2e6db36c0147efbcaf17948bf2301a02cf203b45b9e504 at both ends, on dist/index.d.ts and dist/index.d.cts. The normaliser sorted 80 string-literal union runs. Sorted-union identity holds.
  • Raw sha256: 7a0b7ca10b7be13b346d7034f854267d57a8efbc41350f12aab769c36ee78a88 at both ends, 783,118 bytes. Raw identity holds.
  • The two builds differ where they should.
    • index.js hashes 5376c52e2c80abdd… at base and 5dd2faad4fb4d0c3… at head (index.cjs 84507a06aac8b045… → e347dd6c9b78122c…).
    • foldObjectExtendersFromRegistry.call(host goes 0 → 1 in both bundles.
    • Its function …( declaration goes 0 → 1.
  • Positive control on the head tree. private was dropped from foldObjectExtendersFromRegistry on disk (restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to 04fd085e8a03776c… (normalised aa13275083654b7a…, which differs from the bar's cd4b7bec…) and print foldObjectExtendersFromRegistry(type: string, name: unknown, body: unknown): unknown;. So the bar can see a change on this member.
  • Export tables. The ESM and CJS bundles, loaded at runtime from the package, each export the same 78 names at base and head. The sorted list hashes to 9f18a0177ac736ed… on all of them. Neither the function nor ObjectExtenderFoldHost appears.

The merged tree against main

The merged tree 307e5c9975 was measured against main 6817bf1eae, its second parent, in the same way. The closure was rebuilt by a full pnpm build (116/116), and main's package src/ was swapped in tree-only and restored under a trap. The merged tree differs from main only by this cut: 2 files, +76 / −18.

  • .d.ts: raw 9f7d69b1624e9398… (784,351 bytes) and normalised fd5431a235098745…, the same at main and merged. Both identities hold. The offset trap did not fire after fix(metadata-protocol)!: refuse the per-item publish and a package-less save over a binding to an uninstalled package #27084's protocol.ts change.
  • index.js: aa69b0ce794c8221… at main, 18aded551747cdaa… merged. Export tables: 78 names, 9f18a0177ac736ed… on both.
  • AST compare, main's protocol.ts against the merged one: 112 tokens, 0 differ.
  • Suite: main and merged both give 21,775 tests (21,749 passed, 0 failed, 26 skipped, 281 files), per test identical.

The second merge 752f03c260 was measured the same way against main 6845035c6b, with the same .d.ts result.

No changeset: skip-changeset, per ruling 6348891160.

The durability --list

check-durability-degradation-log-level exits 0 at both ends with identical output: 89 read seam(s) at both ends. Its --list is identical once [@:][0-9]+ is stripped (165 lines, and no entry changes file).

  • The moved try guards the in-memory registry.foldObjectExtendersOnto call, not the gate's read vocabulary (find/findOne/count).
  • No seam enters or leaves the census.
  • On the merged tree the list is identical too, 89 read seam(s).

Proof of move-only

Body, by AST token. The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters (less the added this), the return type and the body. It compares base protocol.ts at cbb3a1a1a5 against the head module:

foldObjectExtendersFromRegistry: base tokens 112, head tokens 112, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=112 differ=0 members=1
CLASS members base=304 head=304 order-same=true byte-identical=303 stubs-with-identical-docblock+signature=1 other=[]
TOP base=263 head=264 byte-identical=263

Negative controls. Each changed one token in an in-memory copy of the head module, and the member moved (differ 1):

"if (singular !== 'object') return body;"   -> "… !== 'view' …"     differ 1
"foldObjectExtendersOnto(name, body)"        -> "…(body, body)"       differ 1
  • Class side. There are 304 members at base and at head, in the same order. The 303 that did not move are byte-identical to base, leading comments included. The stub sits at its base position and keeps its docblock and signature byte-identical.
  • Top level of protocol.ts. All 263 base statements are byte-identical, leading comments included. Head adds one statement, the new module's import.
  • Size. +76 / −18 over 2 files, 94 changed lines (the claim's estimate was 60–90).

Readers of the moved lines

These were grepped for the member name and the body's tags and distinctive text (foldObjectExtendersOnto, #8027, #7556):

  • the 24 files that name protocol.ts and call readFileSync;

  • the test files in packages/, docs/qa/ and scripts/;

  • the @objectstack/spec liveness ledgers.

  • By path: none reads a moved line.

  • The one other mention. protocol.runtime-gate-stored-universe.test.ts names the member in a comment only. It and the doors that serve an object by name are driven by the package suite, which passes at both ends.

  • The liveness ledgers. No row points at the member. Carried forward: manifest.json's dependencies row → protocol.ts#resolveWritePackageScope (cut 19), and seed.json → protocol.ts#applySeedBodies (cut 14). These are packages/spec/**, outside this card.

  • ADR anchors. check-adr-anchors exits 0 with identical output at both ends (1,898 decision-letter citations, 60,136 citations). No moved line cites an ADR.

  • Engine-double contract (check-engine-double-contract): exits 0 at both ends.

    • The updateData/deleteData seams keep their entries at new line numbers (14009 → 13999, 14034 → 14024).
    • productionFiles is 3,826 at base and 3,828 at head. The +2 are the new file and the gitignored packages/spec/src/migrations/registry.ts that the spec build writes in the head tree, as in cuts 20–29.
  • Identical output at both ends: check-adr-anchors, check-adr-symbol-anchors, check-system-context-census, check-tenant-audit-census, check-query-options-erasure-ratchet, check-filter-alias-parity and check-durability-degradation-log-level. check-nul-bytes differs only in the scanned file count, +1. The base-end readings were taken in a clean detached worktree at cbb3a1a1a5.

Verification

  • tsc --noEmit in packages/metadata-protocol exits 0 at head and on the merged tree.
  • pnpm exec eslint --no-inline-config packages/metadata-protocol exits 0 with no output (the whole touched package, with the root lint script's flags).
  • packages/metadata-protocol suite, compared per test name and status. Each leg ran vitest --maxWorkers=2 with the JSON reporter under the shared verify lock.
    • Base cbb3a1a1a5: 21,761 tests (21,735 passed, 0 failed, 26 skipped), 280 files.
    • Head b542764e48: 21,761 tests (21,735 passed, 0 failed, 26 skipped), 280 files.
    • None lost, none added, none changed status. The base leg is the package src/ swapped tree-only from cbb3a1a1a5 (the new module removed) and restored from HEAD under a trap.
    • The merged head 307e5c9975 and main 6817bf1eae both give 21,775 tests, per test identical (0 lost, 0 added, 0 changed).
  • check-platform-checklist --changed-from origin/main (merged tree): OK, 31 anchors judged, 0 lost.
  • Governed surface. check-governed-merges --branch claude/project-thread-ybjm3h finds 0 of 2 paths in the register (13 surfaces), so the PR is NOT governed. It has 94 changed lines, under the 3,000-line human-merge bar.
  • Derived gates. dispatch-gates --commands --repo objectstack-ai/objectstack derives 46 gates on 2 changed paths from 307e5c9975. All 46 exit 0 after the full pnpm build (116/116). dispatch-gates --ran reports 46 derived, 46 run, 0 NOT-MEASURED and 0 UNRUN, and exits 0. The stale-tree finding and its re-derivation are under "Serial with the file's in-flight PRs".

Carry-forward

These stay in protocol.ts:

  • The read-seam members (cut 21): findEnvironmentRows, findEnvironmentRow, packagelessAddress, findServedOverlayRow, foldStoredCollection, applyObjectRegistryMutation, unmanagedImportCollisions, rollBackUnmanagedImport, readRevertableCommit and storedFlowBindingAgrees. rethrowUnlessMetadataStoreUnprovisioned cannot move at all.
  • Public members whose return type is inferred.
  • The bodies kept by a by-path reader:
    • probeRecord, getMetaItemCached, findReferencesToMeta, refuseUnmintableMetaType and metaTypeNamespaceExists.
    • viewContainerNameCollisionRefusal with overlaidShippedContainerViewNames, and reportPageRequiresAbsentAtLoad.
    • The 7 applyRegistryWriteThrough callers, readCodeLayerForCarryForward, ensureObjectStorage, dropObjectStorage and applyRemoteMetadataMutation.
    • emitMetadataMutation, and getOverlayRepo.
    • isArtifactBacked, isNestedArtifactField and packagedArtifactBase (the lookupArtifactItem site pins).
  • The private-static readers: refusePackagedBaseRemoval, packagedBaseRefusal, runPublishSideEffects, promoteDraftForPublish, publishPackageDrafts and discardPackageDrafts.
  • The field writers: warnClosureReadUnavailable and resolveSduiManifest.

🤖 Generated with Claude Code

https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6

…tocol.ts into protocol-object-extender-fold.ts (#25467 cut 30)

Move-only, shape B. foldObjectExtendersFromRegistry moves, unedited, into its
own module. The engine field is handed over as a getter.

Claude-Session: https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 56 of 208 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 — 10 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 e6e41998a8d4543f5935e7c45145dfc3d6fe0e26 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 78ddaaefede4c10a278b361e906f04b24b9b9b21 — the merge of head 307e5c9975026ddc0bfd7e0a27f27411939ed779 into base e6e41998a8d4543f5935e7c45145dfc3d6fe0e26, 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 78ddaaefede4c10a278b361e906f04b24b9b9b21 && git checkout 78ddaaefede4c10a278b361e906f04b24b9b9b21
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e6e41998a8d4543f5935e7c45145dfc3d6fe0e26 307e5c9975026ddc0bfd7e0a27f27411939ed779 && git checkout -B drift-repro e6e41998a8d4543f5935e7c45145dfc3d6fe0e26 && git merge --no-ff 307e5c9975026ddc0bfd7e0a27f27411939ed779

node scripts/docs-audit/affected-docs.mjs --json e6e41998a8d4543f5935e7c45145dfc3d6fe0e26

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-28T07:47Z

Head 307e5c9975 (move b542764e48 plus three main merges), report 6463239559 (on #25467), claim 6460856054. No contract review is owed: there is no packages/spec/** diff, no governed path, no narrowing and no Clause-②: yes.

item reading
Shape Draft, base main, Part of #25467 (cut 30). 2 files: protocol.ts and one new module for the claimed member, protocol-object-extender-fold.ts (foldObjectExtendersFromRegistry). skip-changeset (move-only). Clause-②: no, Metadata-protocol: none.
Move check (seat) All 18 non-blank lines the PR removes from protocol.ts are found verbatim (indent-stripped) in the new module, the try/catch included. The added lines in protocol.ts are the import with its header comment and one stub through ObjectExtenderFoldHost, with engine handed over as a getter. All six callers still call the class member.
Evidence CI green on head 307e5c9975 (34 success, 18 skipped, 0 failed). Governed check: NOT governed, 94 changed lines. The report gives an AST compare of 112 tokens with 0 differ (both negative controls differ by 1), raw and sorted-union .d.ts identity (ruling 6348891160) at both ends and on merged vs main, identical suites, the read-seam census at 89 with an identical --list, and 46 of 46 derived gates.
Disclosed (1) The cut 25 comment-offset trap fired on the first layout: raw .d.ts was 4,967 bytes larger but identical with comments stripped. The dev shortened the cut's own import comment by 21 characters, amended the move commit to b542764e48, got raw identity back, and re-ran every head reading on it. The .d.ts bar was not touched. (2) Three main merges instead of one, because the first two derivations reported STALE TREE; the second brought #27084's protocol.ts change. The derivation re-run on the merge-tree with main 07d18e6927 reports no stale finding and the same 46 commands. No open PR edits protocol.ts at pr_create.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, once every check on this head is green or an expected skip.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 07:48
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 07:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 17c34b8 Oct 28, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ybjm3h branch October 28, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants