Skip to content

Commit 9b242df

Browse files
refactor(metadata-protocol): move the by-name item read's disabled-package face out of protocol.ts into protocol-meta-item-face.ts (#25467 cut 31) (#27181)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X7JKgiM29GUcitRvoGGj5BU)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Cut 31 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–30 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, #27029 and #27129. The claim is `6464007535`. It takes the cut 31 proposal of the cut 30 report (`6463239559`): 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-meta-item-face.ts` (#23050 shape B): `readMetaItemFace`, the one body behind `getMetaItem` and `getMetaItemForExecution`. It does three things: - it refuses an object of a disabled package; - it withholds any other item of a disabled package; - otherwise it answers what `readMetaItem` answers. `protocol.ts` loses 2 net lines (29,495 to 29,493). 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` `1900c02cbe` (after cut 30's merge). It names no issue, so the claim comment is the identity record. It holds the move commit `0f3c00a4d9` and, as the claim asked, one merge of `main`, `d12ae82d8c` (11 commits). That merge touches nothing in `packages/metadata-protocol/`. The merged tree was still measured against `main` (see "The merged tree against `main`"). ## One top-level edit beyond the move: `noPendingDraftError` gains `export` The claim says the four disabled-package helpers "stay imported in `protocol.ts`". They are in fact **`protocol.ts`'s own module-level functions**, declared at `:636`–`:680`. The new module therefore imports them from `./protocol.js`. - `isObjectOfDisabledPackage`, `isItemOfDisabledPackage` and `objectPackageDisabledError` were already exported. - `noPendingDraftError` was not. It gains the `export` keyword in place, with nothing else in its statement changed. This follows `addressPackages` (cut 16) and `withDeclaredPageTypeDefault` (cut 23). - `packages/metadata-protocol/src/index.ts` names its re-exports from `./protocol.js` one by one and does not name it. So the package's published surface does not move: the `.d.ts` and export-table readings below are identical at both ends. - All four keep another reader in `protocol.ts`, and all four stay there. ## Serial with the file's in-flight PRs The claim found no open PR touching `protocol.ts` and asked for a re-check. At `pr_create`, of the 21 open PRs, two touch this package: #26937 (one test) and #26517 (`CHANGELOG.md` / `package.json`). **None edits `protocol.ts`**, none names `readMetaItemFace` or `noPendingDraftError`, and both are merge-tree clean against this head and against `main`. `main` moved 8 commits after the merge (to `b8615afaba`). None touches this package, and `git merge-tree` with this head is clean. The derivation on `d12ae82d8c` found nothing stale, so `main` was not merged again. 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(`. Its two refusals come from the module-level factories `objectPackageDisabledError` and `noPendingDraftError`, which stay in `protocol.ts`, 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 | |---|---|---|---|---|---| | `readMetaItemFace` | `:10702` | private async, return type inferred | 11 | 181 | `engine` (field); `readMetaItem` (method); module-level `isObjectOfDisabledPackage`, `objectPackageDisabledError`, `isItemOfDisabledPackage`, `noPendingDraftError` | `protocol-meta-item-face.ts` has 70 lines. ## The shape (#23050 shape B, as cuts 1–30 apply it) - **The class keeps the member.** It keeps its name, docblock, signature and position. Its body now delegates, marked `[#25467 cut 31]`. - **`MetaItemFaceHost`** types the two members the body reads as the class members themselves (`ObjectStackProtocolImplementation['engine']` and `['readMetaItem']`). - **The `engine` field is handed over as a getter**, so the read finds it at call time, as before. - **The call to `readMetaItem` is handed over as a closure**, `(...a) => this.readMetaItem(...a)`, as the claim asked. So that call still reaches the class method. - **The return type stays inferred.** The stub returns `readMetaItemFace.call(host, request, face)`, so the method's inferred type is the moved function's inferred type. The member is private, so the `.d.ts` prints its name only, and the positive control below shows the full type when it is not. - **No member changes visibility.** - **Every caller still calls the class method.** They are `getMetaItem` (`:10653`) and `getMetaItemForExecution` (`:10688`). Neither changes. ## 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**, including on the merged tree against `main`. The cut 25 / cut 30 comment-offset trap was checked first: a `metadata-protocol` build right after the move commit gave the base's raw hash. It did not fire. Base `1900c02cbe` and head `0f3c00a4d9` 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): `fd5431a235098745024b527517710c81a74652d9f8d4b842a691e9d3009207ef` 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**: `9f7d69b1624e93986442f4d5b558d8976671c791ec19d84dda5eb68b4249128b` at both ends, 784,351 bytes. **Raw identity holds**, with `noPendingDraftError` now exported from `protocol.ts`. - **The two builds differ where they should.** - `index.js` hashes `18aded551747cdaa…` at base and `b4863907a762ba0b…` at head (`index.cjs` `d859527bb8d3b7f1…` → `d04d03f27652735c…`). - `readMetaItemFace.call(host` goes 0 → 1 in both bundles. - Its `function …(` declaration goes 0 → 1. - **Positive control** on the head tree. `private` was dropped from `readMetaItemFace` on disk (restored from `HEAD` under a trap) and the package rebuilt. Both `.d.ts` files move to `74f2db843cbc8a66…` (normalised `b6f0bbbad4cbbe16…`, which differs from the bar's `fd5431a2…`) and print `readMetaItemFace(request: {…` with its inferred return type. 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. None of `readMetaItemFace`, `MetaItemFaceHost` or `noPendingDraftError` appears. ### The merged tree against `main` The merged tree `d12ae82d8c` was measured against `main` `3346da26df`, 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, +80 / −12. - **`.d.ts`**: raw `9f7d69b1624e9398…` (784,351 bytes) and normalised `fd5431a235098745…`, the same at `main` and merged. Both identities hold. - **`index.js`**: `18aded551747cdaa…` at `main`, `b4863907a762ba0b…` merged. Export tables: 78 names, `9f18a0177ac736ed…` on both. - **AST compare**, `main`'s `protocol.ts` against the merged one: 181 tokens, 0 differ. - **Suite**: `main` and merged both give 21,775 tests (21,749 passed, 0 failed, 26 skipped, 281 files), per test identical. 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 body has no `try`, and it calls none of 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 `1900c02cbe` against the head module: ``` readMetaItemFace: base tokens 181, head tokens 181, differ 0, text-equal(ws-collapsed, comments incl.) true TOTAL tokens=181 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=264 head=265 byte-identical=263 TOP base-only: [ "function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } {" ] TOP head-only: [ "import { readMetaItemFace } from './protocol-meta-item-face.js';", "export function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } {" ] ``` **Negative controls.** Each changed one token in an in-memory copy of the head module, and the member moved (differ 1): ``` "request.type === 'package'" -> "request.type === 'object'" differ 1 "request.state === 'draft'" -> "request.state === 'active'" 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`.** Of the 264 base statements, 263 are byte-identical, leading comments included. The other is `noPendingDraftError`, whose only change is the added `export` (see above). Head adds one statement, the new module's import. - **Size.** +80 / −12 over 2 files, 92 changed lines (the claim's estimate was 70–100). ## Readers of the moved lines These were grepped for the member name, the four helpers and the body's distinctive text (`_withheld`, `_servedOnly`): - 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.item-execution-read.test.ts` names the member in a comment and drives both faces through `getMetaItem` / `getMetaItemForExecution`. It passes at both ends, in the package suite. - **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,900 decision-letter citations, 60,175 citations). - **Engine-double contract** (`check-engine-double-contract`): exits 0 at both ends. - The `updateData`/`deleteData` seams keep their entries at new line numbers (13999 → 13997, 14024 → 14022). - `productionFiles` is 3,832 at base and 3,834 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–30. - **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 `1900c02cbe`. ## 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 `1900c02cbe`: 21,775 tests (21,749 passed, 0 failed, 26 skipped), 281 files. - Head `0f3c00a4d9`: 21,775 tests (21,749 passed, 0 failed, 26 skipped), 281 files. - None lost, none added, none changed status. The base leg is the package `src/` swapped tree-only from `1900c02cbe` (the new module removed) and restored from `HEAD` under a trap. - The merged head `d12ae82d8c` and `main` `3346da26df` 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 92 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 `d12ae82d8c`, with no stale-tree finding. 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. ## 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.com/claude-code) https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent c07f034 commit 9b242df

2 files changed

Lines changed: 80 additions & 12 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 31] The by-name item read's disabled-package face, from
5+
* `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole:
6+
* `readMetaItemFace`, the one body behind `getMetaItem` and
7+
* `getMetaItemForExecution`. It refuses an object of a disabled package,
8+
* withholds any other item of one, and otherwise answers what `readMetaItem`
9+
* answers.
10+
*
11+
* A MOVE, not a redesign. The function below is the body of the
12+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
13+
* re-indented one level and otherwise unedited.
14+
*
15+
* ## How the moved body reaches the class
16+
*
17+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
18+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
19+
* The class keeps the method — the published `dist/*.d.ts` lists its name,
20+
* docblock and declared signature — and its body is one literal of exactly
21+
* the {@link MetaItemFaceHost} members it reads, plus a `.call`. The `engine`
22+
* field is handed over as a getter, so the read finds it as it stands at call
23+
* time; the call to `readMetaItem` is handed over as a closure, so it still
24+
* reaches the class method. Every caller keeps calling the class method.
25+
*
26+
* The disabled-package helpers it reads are `protocol.ts`'s own, and are
27+
* imported from there. Nothing re-exports this module, so the published
28+
* surface does not move.
29+
*/
30+
31+
import type { ObjectStackProtocolImplementation } from './protocol.js';
32+
import {
33+
isItemOfDisabledPackage,
34+
isObjectOfDisabledPackage,
35+
noPendingDraftError,
36+
objectPackageDisabledError,
37+
} from './protocol.js';
38+
39+
/**
40+
* The `ObjectStackProtocolImplementation` members the function below reads —
41+
* every one of them, and no other; the function's `this` is the `Pick` of
42+
* them. Each is typed AS the class member (an indexed access on the class),
43+
* so a change to a member's signature on the class is a change here too.
44+
*/
45+
export interface MetaItemFaceHost {
46+
readonly engine: ObjectStackProtocolImplementation['engine'];
47+
readonly readMetaItem: ObjectStackProtocolImplementation['readMetaItem'];
48+
}
49+
50+
/** The body of `ObjectStackProtocolImplementation#readMetaItemFace`; the method's docblock there is its contract. */
51+
export async function readMetaItemFace(
52+
this: Pick<MetaItemFaceHost, 'engine' | 'readMetaItem'>,
53+
request: {
54+
type: string, name: string, packageId?: string, state?: 'active' | 'draft', previewDrafts?: boolean,
55+
member?: string,
56+
},
57+
face: 'served' | 'execution',
58+
) {
59+
if (request.type === 'object' && isObjectOfDisabledPackage(this.engine?.registry, request.name)) {
60+
throw objectPackageDisabledError(request.name);
61+
}
62+
const answer = await this.readMetaItem(request, { face });
63+
if (request.type === 'object' || request.type === 'package'
64+
|| !isItemOfDisabledPackage(this.engine?.registry, answer?.item)) {
65+
return answer;
66+
}
67+
if (request.state === 'draft') throw noPendingDraftError(request.type, request.name);
68+
const { item: _withheld, registration: _servedOnly, ...envelope } = answer as typeof answer & { registration?: unknown };
69+
return { ...envelope, item: undefined };
70+
}

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ import { isDeclaredCodeDatasource, isHostCodeDatasource } from './protocol-code-
425425
import { legacyOrganizationRowsOf, withoutLegacyOrganizationRows } from './protocol-legacy-organization-rows.js';
426426
// [#25467 cut 30] The object-extender fold onto a MetadataService object body.
427427
import { foldObjectExtendersFromRegistry } from './protocol-object-extender-fold.js';
428+
// [#25467 cut 31] The disabled-package face of the by-name item read.
429+
import { readMetaItemFace } from './protocol-meta-item-face.js';
428430

429431
/**
430432
* Canonical Zod schema per metadata type lives in
@@ -677,7 +679,7 @@ export function isItemOfDisabledPackage(registry: any, item: unknown): boolean {
677679
* finds no draft row and the read of a disabled package's draft, so the two
678680
* answers are byte-identical.
679681
*/
680-
function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } {
682+
export function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } {
681683
const err = new Error(`No pending draft exists for ${type}/${name}.`) as Error & { code: 'NO_DRAFT'; status: 404 };
682684
err.code = 'NO_DRAFT';
683685
err.status = 404;
@@ -10703,17 +10705,13 @@ export class ObjectStackProtocolImplementation implements
1070310705
type: string, name: string, packageId?: string, state?: 'active' | 'draft', previewDrafts?: boolean,
1070410706
member?: string,
1070510707
}, face: 'served' | 'execution') {
10706-
if (request.type === 'object' && isObjectOfDisabledPackage(this.engine?.registry, request.name)) {
10707-
throw objectPackageDisabledError(request.name);
10708-
}
10709-
const answer = await this.readMetaItem(request, { face });
10710-
if (request.type === 'object' || request.type === 'package'
10711-
|| !isItemOfDisabledPackage(this.engine?.registry, answer?.item)) {
10712-
return answer;
10713-
}
10714-
if (request.state === 'draft') throw noPendingDraftError(request.type, request.name);
10715-
const { item: _withheld, registration: _servedOnly, ...envelope } = answer as typeof answer & { registration?: unknown };
10716-
return { ...envelope, item: undefined };
10708+
// [#25467 cut 31] The body moved to `protocol-meta-item-face.ts` (see `MetaItemFaceHost`).
10709+
const readEngine = () => this.engine;
10710+
const host: ThisParameterType<typeof readMetaItemFace> = {
10711+
get engine() { return readEngine(); },
10712+
readMetaItem: (...a) => this.readMetaItem(...a),
10713+
};
10714+
return readMetaItemFace.call(host, request, face);
1071710715
}
1071810716

1071910717
/**

0 commit comments

Comments
 (0)