Skip to content

refactor(metadata-protocol): move the object-owner verdicts out of protocol.ts into protocol-object-owner.ts (#25467 cut 17) - #26264

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ybjm3h
Oct 26, 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 17 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cuts 1–16 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104 and #26168. The claim is 6421301816; the scope is the cut 17 proposal of the cut 16 report (6418840219), as the server seat approved it in its dispatch order. Both members passed the AST screen and moved unedited, so no member was left behind. ensureObjectStorage stays where it is, per the order.

Two ObjectStackProtocolImplementation members move unedited into a new module, packages/metadata-protocol/src/protocol-object-owner.ts (#23050 shape B). They are the object-owner verdicts:

  • classifyObjectContribution: which contributor kind (own, overlay or mismatch) a stored object row registers as, judged against the registry's packaged owner of that name, or its runtime owner when there is none.
  • releaseAdoptedObjectOwner: hands an adopted object's name from the 'sys_metadata' sentinel owner to its rebound row.

protocol.ts loses 37 net lines (29,327 to 29,290). 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, restarted from main df8aac41f2 after cut 16 merged. It names no issue, so the claim comment is the identity record. It holds the move commit 4d5121bee and three merges of main: f2e7083695 (1c5dda251), then 81bba6c3f5 (2c409efd84) and 3d50a43bcb (5e5bf4438e), the last two because dispatch-gates printed STALE TREE for gate sources that main had changed. No merge touches a file of packages/metadata-protocol/src/: the package's src/ tree is 575f85d7 at the move commit and at every merge. The merges do change packages/metadata-protocol/package.json (sideEffects: false and the repository URL) and its tsconfig.json (now extends tsconfig.base.json), so the workspace was rebuilt in full after them and the package re-measured (below).

Serial with the file's in-flight PRs. Measured just before this PR was opened: of the 36 open PRs, two touch packages/metadata-protocol/src/.

No open PR edits the new module.

The order's screen (private statics, new Error()

Both members passed:

  • No private-static read. The AST walk found no access on the ObjectStackProtocolImplementation identifier and none on this.constructor in either body.
  • No error constructed. Neither body has a new Error( or any other error constructor, so no PRODUCERS row is owed. releaseAdoptedObjectOwner has one catch, which logs through console.error and answers false; it moves with its body (see the durability --list below).

What moves

Member Base line Visibility Body lines AST tokens (params + return + body)
classifyObjectContribution :17512 private 29 221
releaseAdoptedObjectOwner :26125 private 22 185

51 body lines (the proposal said about 55).

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

  • The class keeps every member with its name, docblock, signature and position. Each body now delegates, marked [#25467 cut 17].
  • Typing. ObjectOwnerHost types its one member as the class member itself (ObjectStackProtocolImplementation['engine']). Each moved function types its this as Pick<ObjectOwnerHost, 'engine'>, and each stub annotates its literal as ThisParameterType of typeof fn. No member changes visibility.
  • Members read, measured by AST walk (property and element accesses on this, with as casts, parentheses and ! stripped; bare this checked too): both bodies read engine only (through (this.engine as any)?.registry). It is handed over as a live getter, so the body reads the protocol's engine, and so its registry, at call time.
  • Every other caller still calls the class method: classifyObjectContribution from applyObjectRegistryMutation, saveMetaItem, promoteDraftForPublish and loadMetaFromDb (4 this. call sites), and releaseAdoptedObjectOwner from reassignOrphanedMetadata (1).

Module-level declarations

  • None moves and none gains export. Neither body reads a module-level declaration of protocol.ts, and neither reads an import. The new module imports only the class type (import type from ./protocol.js), so it adds no runtime edge to the protocol.ts cycle.
  • Imports in protocol.ts. No import loses its last reader. The one new statement is the import of the new module.

The durability --list

releaseAdoptedObjectOwner's catch moved file, so the list was read at both ends. check-durability-degradation-log-level exits 0 at both ends with identical output, and its --list is identical once [@:][0-9]+ is stripped (159 lines; no entry changes file, and no entry names either moved member at either end). The catch guards registry.unregisterObject, a registry call, and the list names no entry for it at either end.

The published .d.ts

The series bar is ruling 6348891160, sorted-union identity, measured first. Base df8aac41f2 and head 4d5121bee were built with pnpm --filter @objectstack/metadata-protocol build against one dependency tree. The closure was built at head by a full pnpm build (119/119). For the base build, the package's src/ was swapped tree-only to base and restored from HEAD under a trap; git diff HEAD was empty, git status clean, and both touched blobs equal to HEAD's after. Measured on the pre-merge tree, and again after each merge of main (heads 1c5dda251, 2c409efd84 and 5e5bf4438e, the last two after a full pnpm build, 119/119). The readings below hold for every one.

  • Raw sha256: 08914e65f936a2b031c3f3f215481a97f53d132eefd869e4f42d928947ebe173 at base and at head, on both dist/index.d.ts and dist/index.d.cts, 775,874 bytes each. Raw identity holds, so sorted-union identity holds too. Both members stay private NAME; lines.
  • Normalised sha256: 46ccd7e5c4db673652513d1801ec9c9f06bf34a0a7fb6ba97fb2531ef0cc544b at both ends (80 string-literal union runs sorted; the cut 12 normaliser).
  • The two builds differ where they should: index.js hashes bfb1ef96fc8499bc… at base and 8732ec7d7e0d5cd5… at head. The .call(host sites of classifyObjectContribution and releaseAdoptedObjectOwner occur 0 times in the base bundles and once each in the head bundles (ESM and CJS alike).
  • Positive control on the head tree: private was dropped from classifyObjectContribution (on disk, restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to c685a8419f36b078… (normalised 524c41057de3c18c…) and print classifyObjectContribution(name: string, rowPackageId: string | null | undefined): { kind: 'own' | 'overlay'; …, so the bar can see a change on these members.
  • 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 four. Neither moved function nor ObjectOwnerHost appears.

No changeset: skip-changeset, per ruling 6348891160.

Proof of move-only

Bodies, by AST token (the TypeScript compiler's AST leaf tokens, JSDoc skipped, over parameters less the added this, return type and body), base protocol.ts at df8aac41f2 against the head module:

classifyObjectContribution: base tokens 221, head tokens 221, differ 0, text-equal(ws-collapsed, comments incl.) true
releaseAdoptedObjectOwner: base tokens 185, head tokens 185, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=406 differ=0 members=2
CLASS members base=300 head=300 order-same=true byte-identical=298 stubs-with-identical-docblock+signature=2 other=[]
TOP base=245 head=246 byte-identical=245

Negative control, one token changed in an in-memory copy of the head module (binding !== ownerPackageId to binding === ownerPackageId, one occurrence):

MUTATED packages/metadata-protocol/src/protocol-object-owner.ts "binding !== ownerPackageId" -> "binding === ownerPackageId"
classifyObjectContribution: base tokens 221, head tokens 221, differ 1, text-equal(ws-collapsed, comments incl.) false
releaseAdoptedObjectOwner: base tokens 185, head tokens 185, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=406 differ=1 members=2
  • Class side. 300 members at base and at head, in the same order. The 298 that did not move are byte-identical to base, leading comments included. Each of the 2 stubs sits at its base position and keeps its docblock and signature byte-identical (everything before the body's opening brace).
  • Top level of protocol.ts. All 245 base statements are byte-identical, leading comments included. Head adds one statement, the new module's import.
  • Size. +125 / −51 over 2 files, 176 changed lines (the proposal's estimate was 200–260). protocol-object-owner.ts has 111 lines.

Readers of the moved lines

Every test and script that reads protocol.ts by path was grepped for the two member names. So was every test file in packages/ and ui/, and docs/qa/.

  • By path: none reads a moved body. protocol.object-registry-write-through-spelling.test.ts names releaseAdoptedObjectOwner only in a comment (line 67); neither moved body holds a spelling it counts, and it passes at both ends (in the package suite).
  • Behaviour through the class method, no edit owed. Three packages/objectql tests name classifyObjectContribution in their headers and drive it through the save and hydration doors: protocol-runtime-owner-overlay-refusal.test.ts, protocol-sentinel-owner-draft-save-address.test.ts and protocol-sentinel-owner-package-save-refusal.test.ts. Run at head after the full build: 3 files, 27 tests passed.
  • No hit in docs/qa/.
  • ADR anchors and decision letters. check-adr-anchors exits 0 with identical output at both ends. Neither body cites an ADR; their [#23416] and [#23514] comments are issue citations, which move with them.
  • Engine-double contract (check-engine-double-contract): exits 0 at both ends. The updateData/deleteData seams keep their entries at new line numbers (13974 → 13976, 13999 → 14001; the two-line import comes before them). productionFiles is 3,733 at base and 3,735 at head: +1 is the new file, +1 is the gitignored packages/spec/src/migrations/registry.ts the spec build writes in the head tree.
  • Identical output at both ends: check-adr-symbol-anchors, check-system-context-census, check-tenant-audit-census, check-query-options-erasure-ratchet, check-filter-alias-parity, 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 df8aac41f2.
  • Governed surface. check-governed-merges --branch claude/project-thread-ybjm3h: 0 of 2 paths hit the register, NOT governed; 176 changed lines, under the 3,000-line human-merge bar.

Verification

  • tsc --noEmit in packages/metadata-protocol exits 0 before and after the merges. pnpm exec eslint --no-inline-config packages/metadata-protocol (the whole touched package; it has no lint script, these are the root lint script's flags) exits 0, no output.
  • packages/metadata-protocol suite, base and head, compared per test name and status (vitest --maxWorkers=2, JSON reporter, each run under the shared verify lock, VERDICT command-exit 0 both legs), on the pre-merge tree (no merge changed a file of the package's src/):
    • Base df8aac41f2: 21,558 tests (21,539 passed, 0 failed, 19 skipped), 269 files.
    • Head 4d5121bee: 21,558 tests (21,539 passed, 0 failed, 19 skipped), 269 files.
    • None lost, none added, none changed status. The base leg is the package src/ swapped tree-only from df8aac41f2 (the new module removed) and restored from HEAD under a trap.
  • check-platform-checklist --changed-from origin/main (merged tree): OK, 1 anchored file in the change set, 29 anchors judged, 0 lost.
  • Derived gates. dispatch-gates --commands --repo objectstack-ai/objectstack derives 53 gates on 2 changed paths from the merged tree 5e5bf4438e, with no stale-tree warning; all 53 exit 0 after the full pnpm build (119/119). dispatch-gates --ran: 53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. (The same 53 also all exited 0 on the first merged tree 1c5dda251, before the STALE TREE re-merges.)

Carry-forward

Unchanged from cut 16. These stay in protocol.ts:

  • Public members whose return type is inferred.
  • The bodies kept by a reader: probeRecord, getMetaItemCached, findReferencesToMeta, packagelessAddress, refuseUnmintableMetaType, metaTypeNamespaceExists, viewContainerNameCollisionRefusal with overlaidShippedContainerViewNames, reportPageRequiresAbsentAtLoad, the 7 applyRegistryWriteThrough callers, and readCodeLayerForCarryForward.
  • The private-static readers: refusePackagedBaseRemoval, packagedBaseRefusal, runPublishSideEffects, promoteDraftForPublish, publishPackageDrafts and discardPackageDrafts.
  • The field writers: warnClosureReadUnavailable.
  • ensureObjectStorage, the object-owner family's other member, stays per the order (its pin would need widening).

🤖 Generated with Claude Code

https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6

…otocol.ts (#25467 cut 17)

Move the bodies of classifyObjectContribution and releaseAdoptedObjectOwner
unedited into protocol-object-owner.ts, behind an ObjectOwnerHost that hands
over the engine as a getter. The class keeps both methods, their docblocks
and declared signatures; each body becomes a host literal plus a .call.

Claude-Session: https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6
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 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

17 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 48fba2b178f896fde34a72f073fcbbb220af5d30.

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

What this run could not see
  • 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 48fba2b178f896fde34a72f073fcbbb220af5d30 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 86585f55019dbdce870c260fc695b0eced837b82 — the merge of head 5e5bf4438e39cf2e8ea0c18ee5bf4eeb2a5e7202 into base 48fba2b178f896fde34a72f073fcbbb220af5d30, 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 86585f55019dbdce870c260fc695b0eced837b82 && git checkout 86585f55019dbdce870c260fc695b0eced837b82
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 48fba2b178f896fde34a72f073fcbbb220af5d30 5e5bf4438e39cf2e8ea0c18ee5bf4eeb2a5e7202 && git checkout -B drift-repro 48fba2b178f896fde34a72f073fcbbb220af5d30 && git merge --no-ff 5e5bf4438e39cf2e8ea0c18ee5bf4eeb2a5e7202

node scripts/docs-audit/affected-docs.mjs --json 48fba2b178f896fde34a72f073fcbbb220af5d30

⚠️ 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 48fba2b178f896fde34a72f073fcbbb220af5d30 → 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

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-26T18:24Z

Head 5e5bf4438e, report 6423385238 (on #25467), claim 6421301816. No contract review is owed, because there is no packages/spec/** diff and no narrowing.

item reading
Shape Draft, base main, Part of #25467 (cut 17), skip-changeset (no published behaviour moves), mergeable clean.
Files 2 files, +125/−51. classifyObjectContribution and releaseAdoptedObjectOwner move into protocol-object-owner.ts (#23050 shape B). Their class members stay as delegating stubs, so the 4 + 1 this. call sites are unchanged. Both bodies read engine only, handed over as a live getter. Not governed.
Fix A pure move. Seat probe: all 51 removed protocol.ts lines reappear verbatim in the new module, 0 missing. No module-level declaration moves or gains export. Dev: AST compare 406 tokens, 0 differ (the negative control gives 1). Class side: 300 members, 298 byte-identical and 2 stubs with identical docblock and signature. .d.ts is raw-identical at both ends (08914e65…) after every merge, and the positive control moves it. Export tables are equal at 78 names.
Order questions Neither body reads a private static or constructs an error, so no PRODUCERS row is owed. releaseAdoptedObjectOwner's catch moves file; the durability --list is identical once line numbers are stripped. ensureObjectStorage stays, per the order. The three main merges touch no file under the package's src/.
Evidence 36 check runs succeeded and 16 were expected skips on this head, 0 failed. The suite ran 21,558 tests at both ends, with 0 lost, 0 added and 0 changed. Derived gates: 53 of 53 exit 0 on the merged tree, and --ran reconciles.
Carried Cut 18 goes to the same thread once this lands: flowSaveReplaysRetired (about 55 lines, reads getOverlayRepo; FLOW_FALLBACK_VERDICT_NOT_REPLAYED gains export, and ALL_CONVERSIONS leaves the import if it loses its last reader).
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, now that 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 26, 2026 18:26
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 860df72 Oct 26, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ybjm3h branch October 26, 2026 19:43
This was referenced Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m 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