Skip to content

refactor(metadata-protocol): move the write package scope resolution out of protocol.ts into protocol-write-package-scope.ts (#25467 cut 19) - #26350

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

objectstack-fleet[bot] merged 2 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

This is cut 19 of the protocol.ts split (#24586 ruling A, recorded in 6270097840). Cuts 1–18 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264 and #26317.

The claim is 6427907689. The scope is the cut 19 proposal of the cut 18 report (6426921154), as the server seat approved it in its dispatch order. The member passed the AST screen and moved unedited.

One ObjectStackProtocolImplementation member moves unedited into a new module, packages/metadata-protocol/src/protocol-write-package-scope.ts (#23050 shape B):

  • resolveWritePackageScope resolves a package write's validation scope. That is the written package plus the closure of the dependencies its manifest declares, read from the registry. It answers undefined wherever the registry cannot be read.

protocol.ts loses 30 net lines (29,270 to 29,240). 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 77dc7062e5 after cut 18 merged. It names no issue, so the claim comment is the identity record.

The branch holds the move commit c5f1351978 and the merge of main a8e7393dda (f4eb4330f3). That merge touches no file of packages/metadata-protocol/: the package's src/ tree is e8f66938 at both ends.

Serial with the file's in-flight PRs. Measured just before this PR was opened: of the 29 open PRs, two touch packages/metadata-protocol/src/, and neither names resolveWritePackageScope. Each was checked with git merge-tree against this branch's head (f4eb4330f3).

No open PR edits the new module.

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

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. There is no new Error( and no other error constructor, so no PRODUCERS row is owed. There is no log line.
  • One silent catch around the registry reads. It answers undefined, meaning "an unreadable registry means an unnarrowed gate". It moves with the body. See the durability --list below.

What moves

Member Base line Visibility Body lines AST tokens (params + return + body)
resolveWritePackageScope :7361 private 40 252

The proposal also said 40 body lines.

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

  • The class keeps the member with its name, docblock, signature and position. Its body now delegates, marked [#25467 cut 19].
  • Typing. WritePackageScopeHost types its one member as the class member itself (ObjectStackProtocolImplementation['engine']). The function types its this as Pick<WritePackageScopeHost, 'engine'>, and the stub annotates its literal as ThisParameterType<typeof resolveWritePackageScope>. No member changes visibility.
  • Members read. The AST walk covers property and element accesses on this, with as casts, parentheses and ! stripped, and bare this checked too. It finds engine only, read as this.engine.registry. The engine 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, as the order asks:
    • The authoring gate's host literal at :7107 (cut 8, assertRuntimeAuthoringRules → protocol-authoring-gate.ts) is unchanged. It still hands over resolveWritePackageScope: (...a) => this.resolveWritePackageScope(...a), which reaches the class stub and then the moved body.
    • tenantAuthoredWriteRefusal (:16132) is the other caller, through this..

Module-level declarations

  • None. The body reads no module-level declaration of protocol.ts and no import binding, measured by an AST identifier walk against the file's top-level declarations and imports. Nothing gains export, and no import of protocol.ts changes.
  • The new module imports only the class type (import type from ./protocol.js), so it adds no runtime edge to the protocol.ts cycle. The one new statement in protocol.ts is the import of the new module.

The durability --list (the order's re-measure)

The silent catch moved file, so the list was read at both ends.

  • check-durability-degradation-log-level exits 0 at both ends with identical output.
  • Its --list is identical once [@:][0-9]+ is stripped: 159 lines, and no entry changes file.
  • Neither end has an entry naming resolveWritePackageScope or the new module. The catch guards registry reads (registry.getPackage), not an engine call the list enumerates.

The published .d.ts

The series bar is ruling 6348891160, sorted-union identity, measured first.

How it was built. Base 77dc7062e5 and head c5f1351978 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. Afterwards git diff HEAD was empty, git status was clean, and both touched blobs equalled HEAD's.

The .d.ts was measured twice: on the pre-merge tree, and after merging main (head f4eb4330f3, closure rebuilt by a full pnpm build, 119/119). The readings below hold for both. The base .d.ts differs from cut 18's readings because #26305 landed in between.

  • Raw sha256: a8f6adb6f9c56b8f441b7224ca8c4d43b84891bce0373446e1577a7884079dd8 at base and at head, on both dist/index.d.ts and dist/index.d.cts, 776,660 bytes each. Raw identity holds, so sorted-union identity holds too. The member stays a private NAME; line.
  • Normalised sha256: f7f9306ed46a5605c5e4c59bbc212a7d5288061760ec85d473d2f3047a836ba5 at both ends (80 string-literal union runs sorted; the cut 12 normaliser).
  • The two builds differ where they should:
    • index.js hashes 4f35935b2eaf7f2b… at base and 9495894cdf5ce39b… at head.
    • The .call(host site of resolveWritePackageScope occurs 0 times in the base bundles and once in the head bundles (ESM and CJS alike).
    • function resolveWritePackageScope( appears 0 times at base and once at head.
  • Positive control on the head tree: private was dropped from resolveWritePackageScope (on disk, restored from HEAD under a trap) and the package rebuilt. Both .d.ts files move to cf7f37aa818a9795… (normalised a7662435fc695cad…) and print resolveWritePackageScope(packageId: string | null | undefined): { packageId: string; dependencies: string[]; } | undefined;. 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 four. Neither the moved function nor WritePackageScopeHost appears.

No changeset: skip-changeset, per ruling 6348891160.

Proof of move-only

Body, by AST token. The compare uses the TypeScript compiler's AST leaf tokens, with JSDoc skipped. It covers the parameters (less the added this), the return type and the body. Base protocol.ts at 77dc7062e5 is compared against the head module:

resolveWritePackageScope: base tokens 252, head tokens 252, differ 0, text-equal(ws-collapsed, comments incl.) true
TOTAL tokens=252 differ=0 members=1
CLASS members base=301 head=301 order-same=true byte-identical=300 stubs-with-identical-docblock+signature=1 other=[]
TOP base=247 head=248 byte-identical=247

Negative control. One token was changed in an in-memory copy of the head module (if (dep === packageId || to if (dep !== packageId ||, one occurrence):

MUTATED packages/metadata-protocol/src/protocol-write-package-scope.ts "if (dep === packageId ||" -> "if (dep !== packageId ||"
resolveWritePackageScope: base tokens 252, head tokens 252, differ 1, text-equal(ws-collapsed, comments incl.) false
TOTAL tokens=252 differ=1 members=1
  • Class side. There are 301 members at base and at head, in the same order. The 300 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 (everything before the body's opening brace).
  • Top level of protocol.ts. All 247 base statements are byte-identical, leading comments included. Head adds one statement, the new module's import.
  • Size. +96 / −38 over 2 files, 134 changed lines (the proposal's estimate was 150–200). protocol-write-package-scope.ts has 88 lines.

Readers of the moved lines

I grepped every test and script that reads protocol.ts by path (70 files) for the member name and for its body's text (getPackage, frontier, the catch comment). I also grepped every test file in packages/ and ui/, and docs/qa/.

  • By path: none reads the moved body. Thirteen of them match getPackage, but as a registry test double (getPackage: () => undefined and the like) or a direct registry call (package-dependencies-patch.integration.test.ts), not as the moved text.
  • Comment-only mentions, no edit owed. protocol-publish-drafts-closure.test.ts (line 228), packages/core/src/artifact-packages.ts (line 130) and packages/lint/src/validate-list-view-field-refs.ts (line 272) name the method in comments. It still exists, on the class.
  • The @objectstack/spec liveness ledger. packages/spec/liveness/manifest.json (dependencies row) carries the evidence pointer packages/metadata-protocol/src/protocol.ts#resolveWritePackageScope (\const declared = manifest?.dependencies` inside the frontier loop …)`.
    • check:liveness checks that the #symbol is named in the file. The method is still declared in protocol.ts, so the pointer resolves and pnpm --filter @objectstack/spec check:liveness exits 0 at head.
    • The quoted line itself now lives in protocol-write-package-scope.ts. That is the same state cut 14 left seed.json's protocol.ts#applySeedBodies pointer in.
    • Re-pointing the row is a packages/spec/** edit, which this card does not make. It is disclosed here and in the report as a follow-up.
  • No hit in docs/qa/.
  • ADR anchors. check-adr-anchors exits 0 with identical output at both ends.
  • Engine-double contract (check-engine-double-contract). It exits 0 at both ends.
    • The updateData/deleteData seams keep their entries at new line numbers (13956 → 13926, 13981 → 13951).
    • productionFiles is 3,743 at base and 3,745 at head. One of the +2 is the new file; the other is the gitignored packages/spec/src/migrations/registry.ts that 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 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 77dc7062e5.
  • Governed surface. check-governed-merges --branch claude/project-thread-ybjm3h finds 0 of 2 paths in the register, so this is NOT governed. It has 134 changed lines, under the 3,000-line human-merge bar.

Verification

  • tsc --noEmit in packages/metadata-protocol exits 0 before and after the merge.
  • pnpm exec eslint --no-inline-config packages/metadata-protocol exits 0 with no output. It covers the whole touched package, which has no lint script; these are the root lint script's flags.
  • packages/metadata-protocol suite, base and head, compared per test name and status. Both runs used vitest --maxWorkers=2 with the JSON reporter under the shared verify lock, with VERDICT command-exit 0 on both legs. They ran on the pre-merge tree; the merge changed no file of the package.
    • Base 77dc7062e5: 21,568 tests (21,549 passed, 0 failed, 19 skipped), 270 files.
    • Head c5f1351978: 21,568 tests (21,549 passed, 0 failed, 19 skipped), 270 files.
    • None lost, none added, none changed status. The base leg is the package src/ swapped tree-only from 77dc7062e5 (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 51 gates on 2 changed paths from the merged tree f4eb4330f3. All 51 exit 0 after the full pnpm build (119/119). dispatch-gates --ran: 51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.
    • The derivation flags the tree as behind main only for scripts/sdui-manifest.sources/plugin-grid.json, which feeds none of these 51 families. The list is the same 51 as cut 18's, so main was not merged again.
    • pnpm --filter @objectstack/spec check:liveness exits 0 at head (see the liveness ledger above).

Carry-forward

Unchanged from cut 18. 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, readCodeLayerForCarryForward and ensureObjectStorage.
  • The private-static readers: refusePackagedBaseRemoval, packagedBaseRefusal, runPublishSideEffects, promoteDraftForPublish, publishPackageDrafts and discardPackageDrafts.
  • The field writers: warnClosureReadUnavailable.

🤖 Generated with Claude Code

https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6

…out of protocol.ts (#25467 cut 19)

Move the body of resolveWritePackageScope unedited into
protocol-write-package-scope.ts, behind a WritePackageScopeHost that hands
over the engine as a getter. The class keeps the method, its docblock and
declared signature; its body becomes a host literal plus a .call. The
authoring gate's host closure keeps calling the class method.

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 3 documentable anchor(s).

18 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 83e9d718718508cc27cc3e889ea594b2b0d1d221.

⛔ 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 83e9d718718508cc27cc3e889ea594b2b0d1d221 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 83e9d718718508cc27cc3e889ea594b2b0d1d221

⚠️ 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 83e9d718718508cc27cc3e889ea594b2b0d1d221 → 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-26T22:52Z

Head f4eb4330f3, report 6428860550 (on #25467), claim 6427907689. 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 19), skip-changeset (no published behaviour moves), mergeable clean.
Files 2 files, +96/−38. resolveWritePackageScope moves into protocol-write-package-scope.ts (#23050 shape B). The host is an engine getter only, and the new module imports only the class type. The class member stays as a delegating stub, so the authoring-gate closure at :7107 and every other caller still call the class method. Not governed.
Fix A pure move. Seat probe: all 37 removed protocol.ts lines reappear in the new module, identical once the class indent is stripped, 0 missing. No module-level declaration moves or gains export, and no import changes. Dev: AST compare 252 tokens, 0 differ (the negative control gives 1). .d.ts is raw-identical (a8f6adb6…) before and after the merge, and the positive control moves it. Export tables are equal at 78 names.
Order questions The silent catch moves file. The durability --list is identical once line numbers are stripped, and no entry names the member at either end; the catch guards registry.getPackage, not an engine call. No body constructs an error, so no PRODUCERS row is owed.
Evidence 36 check runs succeeded and 16 were expected skips on this head, 0 failed. The suite ran 21,568 tests at both ends, with 0 lost, 0 added and 0 changed. Derived gates: 51 of 51 exit 0, and --ran reconciles. The merge-tree reading against #26244 and #26138 adds no conflict beyond what they already have with main.
Carried Disclosure, not a defect: packages/spec/liveness/manifest.json's dependencies row still points at protocol.ts#resolveWritePackageScope and quotes a line that now lives in the new module (check:liveness checks only the symbol, which the stub keeps). seed.json's applySeedBodies pointer has been in the same state since cut 14. Re-pointing them is a spec patch, and it is left for the series' close-out. Cut 20 goes to the same thread once this lands: readItemFromMetadataService (about 40 lines, reads getServicesRegistry only).
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

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