Skip to content

Commit dada291

Browse files
refactor(metadata-protocol): move the sealed overlay boot report out of protocol.ts into protocol-sealed-overlay-report.ts (#25467 cut 21) (#26404)
<!-- 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 21 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–20 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350 and #26366. The claim is `6431131976`. It took the cut 21 proposal of the cut 20 report (`6430425388`): the pair `findServedOverlayRow` + `reportSealedOverlayRows`, with the fallback that a member which fails the screen stays and the other moves alone. **This cut moves `reportSealedOverlayRows` alone.** Both members passed the AST screen. Moving `findServedOverlayRow` was built and measured in full, and it would have taken three read seams out of `check-durability-degradation-log-level`'s census (82 → 79) while the gate stayed green. That loss is not move-only, so `findServedOverlayRow` stays in `protocol.ts`. The section "Why `findServedOverlayRow` stays" has the numbers. One `ObjectStackProtocolImplementation` member moves unedited into a new module, `packages/metadata-protocol/src/protocol-sealed-overlay-report.ts` (#23050 shape B): - `reportSealedOverlayRows`: the boot warning `[metadata_sealed_overlay_unserved]`. It names the stored environment `sys_metadata` rows that no read serves because they overlay sealed managed content (ADR-0131 D6): per type, a capped sample of names, and the two remedies. `protocol.ts` loses 20 net lines (29,210 to 29,190 before the merge of `main`; 29,213 to 29,193 after it). 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` `61c079862a` after cut 20 merged. It names no issue, so the claim comment is the identity record. It holds the move commit `be992bfe9d`, the merge of `main` `e931621a1` (`ce32d8d5be`), and the merge of `main` `abb9046057` (`f488ca9c46`). The second merge brought in #26244, which edits `packages/metadata-protocol/` (`seedFile`); it auto-merged in `protocol.ts`, far from the stub, and every reading after it is labelled "merged" below. ## Why `findServedOverlayRow` stays `check-durability-degradation-log-level`'s read-seam rule finds a `try` that guards a storage read. It recognises a read by vocabulary (`find`/`findOne`/`count`) or through a **same-file** wrapper up to `MAX_READ_WRAPPER_DEPTH = 2` hops. `findServedOverlayRow` is such a wrapper: its body reads through `this.findEnvironmentRow(...)`. Three `try` blocks guard calls to it, and the gate counts each as a read seam: ``` packages/metadata-protocol/src/protocol.ts guards findServedOverlayRow() in readMetaItem() → no invented answer packages/metadata-protocol/src/protocol.ts guards findServedOverlayRow() in readMetaItem() → no invented answer packages/metadata-protocol/src/protocol.ts guards findServedOverlayRow() in getMetaItemLayered() → no invented answer ``` With the pair moved (a full build, the AST compare at 0 differ, the package suite 21,568/21,568 and all ten hand gates green), the stub's body is a `.call` into another file, so the wrapper hop ends there. The gate still exited 0, but its `--list` lost exactly those three lines and its verdict went from `82 read seam(s)` to `79 read seam(s)`. Moving `reportSealedOverlayRows` alone leaves the census at 82, with an identical `--list` once `[@:][0-9]+` is stripped. So moving `findServedOverlayRow` waits for one of two things: the gate following an imported wrapper across files, or a ruling that accepts the census loss. Either is a gate decision, outside this move-only card. The cut 22 proposal on #25467 lists the members that a census trial showed to be neutral. ## Serial with the file's in-flight PRs Measured on the pre-merge head, of the 21 open PRs then, one edited `protocol.ts`: **#26244**. Its `git merge-tree` was clean against this head and against `origin/main` alone. It has since merged, and this branch merged it in (`f488ca9c46`, auto-merged, no conflict). Measured again just before this PR was opened, against the merged head `f488ca9c46`: of the 29 open PRs, **none edits `protocol.ts`** and none names `reportSealedOverlayRows`. Two touch `packages/metadata-protocol/src/` test files: **#26337** (3 files) and **#26138** (7 files). Each conflicts with this head only where it already conflicts with `origin/main` alone. No open PR edits the new module. ## The order's screen (private statics, `new Error(`) Both members of the pair passed: - **No private-static read.** The AST walk found no access on the `ObjectStackProtocolImplementation` identifier and none on `this.constructor`. - **No error constructed.** Neither body has `new Error(`, so no `PRODUCERS` row is owed. ## What moves | Member | Base line | Visibility | Body lines | AST tokens (params + return + body) | |---|---|---|---|---| | `reportSealedOverlayRows` | `:28657` | private | 26 | 208 | ## The shape (#23050 shape B, as cuts 1–20 apply it) - **The class keeps the member.** It keeps its name, docblock, signature and position. Its body now delegates, marked `[#25467 cut 21]`. - **No host.** The body reads no member of the class (AST walk over `this` accesses, with `as` casts, parentheses and `!` stripped, and bare `this` checked). So the stub is `return reportSealedOverlayRows(rows);`, a plain call, as `namesWithOwnStoredRow`'s stub is in `protocol-stored-overlay-reads.ts` (cut 16). No member changes visibility. - **The caller still calls the class method.** The one `this.reportSealedOverlayRows(sealedOverlays)` site (`:28549` at base) does not change. ## Module-level declarations, by reader An AST walk over the body's identifiers, against `protocol.ts`'s top-level declarations and import bindings, finds none. The body uses only `console.warn`, `Map` and `Array.from`. The new module imports nothing, nothing gains `export`, and no import of `protocol.ts` changes. The one new statement in `protocol.ts` is the import of the new module. ## The durability `--list` `check-durability-degradation-log-level` exits 0 at both ends. Its `--list` is **identical once `[@:][0-9]+` is stripped** (158 lines; `82 read seam(s)` at both ends, and no entry changes file). The moved body has no `try`, no `catch` and no engine call. Its `console.warn` is not a `catch` handler, so the log-level rule does not grade it. After the merge of `main`, the verdict still reads `82 read seam(s)`. ## 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.** Base `61c079862a` and head `be992bfe9d` 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. - **Normalised sha256** (the ruling's bar): `f7f9306ed46a5605c5e4c59bbc212a7d5288061760ec85d473d2f3047a836ba5` at base and at head, on `dist/index.d.ts` and `dist/index.d.cts`. The normaliser sorted 80 string-literal union runs. **Sorted-union identity holds.** - **Raw sha256**: `1b134685a474f83cfa85a743e49a811d607fcaeed53ae8856dd62db65ad132f5` at base and at head, 776,660 bytes. **Raw identity holds.** (Cut 20's `getDiscovery` union reorder did not recur.) - **Again after the merge, against the `main` it merged.** #26244 changed this package's published surface, so the merged tree was measured against `main` `abb9046057`, not against `61c079862a`. The closure was rebuilt by a full `pnpm build` (119/119) on the merged tree `f488ca9c46`; the `main` leg is the package `src/` swapped tree-only to `abb9046057` and restored from `HEAD` under a trap. Both give raw `059fa1189ac13c65c36d3718584495bebaa48cc545e1526adb37cd95ef7dfaa3` (777,645 bytes) and normalised `0a7694d518b570981c5cdcbcdd8fcb2dec9f1c7d74a7287299fe9e19e32a4183`, on `.d.ts` and `.d.cts`. **Both identities hold on the merged tree too.** - **The two builds differ where they should.** - `index.js` hashes `7be270d93afd5f1e…` at base and `f109362a448bc4d8…` at head (`index.cjs` `b9cafecb264ca263…` → `48b8964ac889f026…`). - `function reportSealedOverlayRows(` appears 0 times in the base bundle and once in the head bundle. On the merged pair, `index.js` is `94affd7c88eb31be…` on `main` and `dbff5f6c5cde69ca…` merged, with the same 0 → 1. - **Positive control** on the head tree. `private` was dropped from `reportSealedOverlayRows` on disk (restored from `HEAD` under a trap) and the package rebuilt. Both `.d.ts` files move to `f631a07ad5d36220…` (normalised `c2f3625e27675e2a…`, which differs from the bar's `f7f9306e…`) and print `reportSealedOverlayRows(rows: ReadonlyArray<{ type: string; name: string; }>): void;`. 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, head and merged. The sorted list hashes to `9f18a0177ac736ed…` on all of them. The moved function does not appear. No changeset: `skip-changeset`, per ruling `6348891160`. ## Proof of move-only **Body, by AST token.** The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters, the return type and the body. It compares base `protocol.ts` at `61c079862a` against the head module: ``` reportSealedOverlayRows: base tokens 208, head tokens 208, differ 0, text-equal(ws-collapsed, comments incl.) true TOTAL tokens=208 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=249 head=250 byte-identical=249 ``` The same compare against `origin/main` `abb9046057` and the merged head gives the same four lines, so the merge added nothing to the cut. **Negative control.** One token was changed in an in-memory copy of the head module: `SAMPLE_PER_TYPE = 5` became `SAMPLE_PER_TYPE = 6`. ``` MUTATED packages/metadata-protocol/src/protocol-sealed-overlay-report.ts "SAMPLE_PER_TYPE = 5" -> "SAMPLE_PER_TYPE = 6" reportSealedOverlayRows: base tokens 208, head tokens 208, differ 1, text-equal(ws-collapsed, comments incl.) false TOTAL tokens=208 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. - **Top level of `protocol.ts`.** All 249 base statements are byte-identical, leading comments included. Head adds one statement, the new module's import. - **Size.** +60 / −24 over 2 files, 84 changed lines. `protocol-sealed-overlay-report.ts` has 56 lines. ## Readers of the moved lines The tests and scripts that read `protocol.ts` by path (33 files that name `protocol.ts` and call `readFileSync`) were grepped for the member name and for its body's text: `SAMPLE_PER_TYPE`, `metadata_sealed_overlay_unserved`, `sealed against overlays`, `linkage-free clone`. So were `docs/qa/` and the `@objectstack/spec` liveness ledgers. - **By path: none reads the moved body.** The one textual hit is the gitignored, build-written `packages/spec/src/migrations/registry.ts`, whose migration prose names the log code; it reads no file. - **Behaviour through the class method, no edit owed.** `protocol.reads-environment-only.test.ts` (metadata-protocol) and `packages/objectql/src/protocol-object-overlay-layer.test.ts` assert the `[metadata_sealed_overlay_unserved]` warning through boot. The first passes at both ends in the package suite. - **The liveness ledgers.** No row points at `reportSealedOverlayRows`. Carried forward: `manifest.json`'s `dependencies` row → `protocol.ts#resolveWritePackageScope` (cut 19) and `seed.json` → `protocol.ts#applySeedBodies` (cut 14) still quote lines that moved to a seam module. `check:liveness` checks only that the `#symbol` is named in the file, and both stubs keep it. Re-pointing them is a `packages/spec/**` edit, outside this card. - **ADR anchors.** `check-adr-anchors` exits 0 at both ends. The warning text's `(ADR-0131 D6)` moves out of the anchored `protocol.ts`, so the decision-letter count goes 1,877 → 1,876. The citation count goes 59,313 → 59,314 and the file count 8,371 → 8,372 (the new module's header names `ADR-0131 D6` once more). The moved line is the warning's text, not an anchor entry paragraph's mechanism, so no anchor entry is owed (as in cuts 8 and 12). The `protocol.ts` anchor's governing ADRs are all still referenced. - **Engine-double contract** (`check-engine-double-contract`): exits 0 at both ends. The `updateData`/`deleteData` seams keep their entries at new line numbers (13896 → 13898, 13921 → 13923, from the two import lines). `productionFiles` is 3,751 at base and 3,753 at head: the new file, and the gitignored `registry.ts` the spec build writes in the head tree (as in cut 20). - **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 `61c079862a`. ## 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**, base and head, compared per test name and status. Each leg ran vitest `--maxWorkers=2` with the JSON reporter under the shared verify lock. - Base `61c079862a`: 21,568 tests (21,549 passed, 0 failed, 19 skipped), 270 files. - Head `be992bfe9d`: 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 `61c079862a` (the new module removed) and restored from `HEAD` under a trap. - Again after the merge, since #26244 changed the package: `main` `abb9046057` (the same tree-only swap) and the merged head `f488ca9c46` both give 21,584 tests (21,565 passed, 0 failed, 19 skipped), 271 files; 0 lost, 0 added, 0 changed. - `check-platform-checklist --changed-from origin/main` (merged tree): OK. 1 anchored file is in the change set, 29 anchors were judged, and 0 were 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 84 changed lines, under the 3,000-line human-merge bar. - **Derived gates.** `dispatch-gates --commands --repo objectstack-ai/objectstack` derives 51 gates on 2 changed paths from the merged tree `f488ca9c46`, with no stale-tree warning; 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. (On the first merge, `ce32d8d5be`, the same 51 also exited 0, but the derivation flagged `scripts/engine-double-contract.pinned.json` as stale against `main`; `main` was merged again and everything after the merge was re-run.) - `check-durability-degradation-log-level --list` on the merged tree: `82 read seam(s)`. - When this PR was opened, `main` was 6 commits past the merge. `dispatch-gates` names 10 changed files there (workflow files and `scripts/ci/**` self-tests). None of them is named by any of this cut's 51 derived gate commands, and none is under `packages/metadata-protocol/`, so `main` was not merged a third time. ## Carry-forward These stay in `protocol.ts`: - **New this cut: the read-seam members.** A member that appears in a `protocol.ts` read-seam entry of `check-durability-degradation-log-level --list` (`guards X() in Y()`), as the guarded wrapper X or as the guarding member Y, takes a seam out of the census when it moves. It stays until the gate follows imported wrappers or a ruling accepts the loss. A census trial (each body moved alone to a scratch module, the gate re-run) measured `findServedOverlayRow` 82 → 79, `packagelessAddress` 82 → 78, and `unmanagedImportCollisions`, `rollBackUnmanagedImport`, `readRevertableCommit` and `applyObjectRegistryMutation` 82 → 81 each. - Public members whose return type is inferred. - The bodies kept by a reader: `probeRecord`, `getMetaItemCached`, `findReferencesToMeta`, `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.com/claude-code) https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 436d284 commit dada291

2 files changed

Lines changed: 60 additions & 24 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 21] The boot report of `ObjectStackProtocolImplementation`
5+
* that says out loud which stored ENVIRONMENT `sys_metadata` rows no read
6+
* serves because they overlay sealed managed content
7+
* (`reportSealedOverlayRows`, ADR-0131 D6): per type, a capped sample of
8+
* names and the two remedies. Moved out of `protocol.ts` whole.
9+
*
10+
* A MOVE, not a redesign. The function below is the body of the
11+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
12+
* re-indented one level and otherwise unedited: the same grouping by type,
13+
* the same sample cap, and the same warning text.
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). The class keeps the method — the
19+
* published `dist/*.d.ts` lists its name, docblock and declared signature.
20+
* The body reads no member of the class, so there is no host: the method's
21+
* stub calls the plain function directly, as `namesWithOwnStoredRow`'s does
22+
* in `protocol-stored-overlay-reads.ts`. Its one caller keeps calling the
23+
* class method.
24+
*
25+
* The body reads none of `protocol.ts`'s module-level declarations and
26+
* imports nothing. Nothing re-exports this module, so the published surface
27+
* does not move.
28+
*/
29+
30+
/** The body of `ObjectStackProtocolImplementation#reportSealedOverlayRows`; the method's docblock there is its contract. */
31+
export function reportSealedOverlayRows(rows: ReadonlyArray<{ type: string; name: string }>): void {
32+
if (rows.length === 0) return;
33+
const SAMPLE_PER_TYPE = 5;
34+
const byType = new Map<string, string[]>();
35+
for (const { type, name } of rows) {
36+
const names = byType.get(type) ?? [];
37+
names.push(name);
38+
byType.set(type, names);
39+
}
40+
const detail = Array.from(byType.entries())
41+
.map(([type, names]) => {
42+
const shown = names.slice(0, SAMPLE_PER_TYPE);
43+
const more = names.length > shown.length ? `, +${names.length - shown.length} more` : '';
44+
return `${type}×${names.length} (${shown.join(', ')}${more})`;
45+
})
46+
.join('; ');
47+
console.warn(
48+
`[Protocol] [metadata_sealed_overlay_unserved] ${rows.length} active environment sys_metadata row(s) ` +
49+
`overlay an item a managed package ships, on a type that is sealed against overlays (ADR-0131 D6): ` +
50+
`${detail}. No read serves them — the package's own definition is served. ` +
51+
`Each row is kept at rest, untouched (an object's table and its columns stay as they are). To keep ` +
52+
`the change, re-express it as a new item under a new ` +
53+
`name (a linkage-free clone of the managed item), then delete the stored row; otherwise delete the ` +
54+
`stored row.`,
55+
);
56+
}

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

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ import { flowSaveReplaysRetired } from './protocol-flow-save-replay.js';
406406
import { resolveWritePackageScope } from './protocol-write-package-scope.js';
407407
// [#25467 cut 20] The code-layer read through the `metadata` service: package-scoped, singular/plural fallback, a stored object row's copy not counted as code.
408408
import { readItemFromMetadataService } from './protocol-metadata-service-read.js';
409+
// [#25467 cut 21] The boot report of the stored overlay rows no read serves because they overlay sealed managed content.
410+
import { reportSealedOverlayRows } from './protocol-sealed-overlay-report.js';
409411

410412
/**
411413
* Canonical Zod schema per metadata type lives in
@@ -28658,30 +28660,8 @@ export class ObjectStackProtocolImplementation implements
2865828660
* sealed overlays are still not served.
2865928661
*/
2866028662
private reportSealedOverlayRows(rows: ReadonlyArray<{ type: string; name: string }>): void {
28661-
if (rows.length === 0) return;
28662-
const SAMPLE_PER_TYPE = 5;
28663-
const byType = new Map<string, string[]>();
28664-
for (const { type, name } of rows) {
28665-
const names = byType.get(type) ?? [];
28666-
names.push(name);
28667-
byType.set(type, names);
28668-
}
28669-
const detail = Array.from(byType.entries())
28670-
.map(([type, names]) => {
28671-
const shown = names.slice(0, SAMPLE_PER_TYPE);
28672-
const more = names.length > shown.length ? `, +${names.length - shown.length} more` : '';
28673-
return `${type}×${names.length} (${shown.join(', ')}${more})`;
28674-
})
28675-
.join('; ');
28676-
console.warn(
28677-
`[Protocol] [metadata_sealed_overlay_unserved] ${rows.length} active environment sys_metadata row(s) ` +
28678-
`overlay an item a managed package ships, on a type that is sealed against overlays (ADR-0131 D6): ` +
28679-
`${detail}. No read serves them — the package's own definition is served. ` +
28680-
`Each row is kept at rest, untouched (an object's table and its columns stay as they are). To keep ` +
28681-
`the change, re-express it as a new item under a new ` +
28682-
`name (a linkage-free clone of the managed item), then delete the stored row; otherwise delete the ` +
28683-
`stored row.`,
28684-
);
28663+
// [#25467 cut 21] The body moved to `protocol-sealed-overlay-report.ts`.
28664+
return reportSealedOverlayRows(rows);
2868528665
}
2868628666

2868728667
// ==========================================

0 commit comments

Comments
 (0)