Skip to content

Commit 60d8fd2

Browse files
refactor(metadata-protocol): move the lock gate and the OCC write addresses out of protocol.ts into protocol-lock-version-gates.ts (#25467 cut 5) (#25601)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu2dTVig4NQQadyC11Fs5BFZ)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Cut 5 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–4 landed as #25494, #25518, #25538 and #25589. The claim is `6379699184`; the seat agreed the scope before it, and its two pre-claim conditions (the `.d.ts` on the trial build, the governed-surface predicate) are answered in the claim and below. Sixteen PRIVATE `ObjectStackProtocolImplementation` methods, the lock gate and the optimistic-concurrency write addresses, move unedited into a new module, `packages/metadata-protocol/src/protocol-lock-version-gates.ts`, with the two declarations only they read. On base `ba6912c1ad` that is 278 body lines, 122 of them from the last 30 days. It is move-only, and the card stays open for the later cuts. Branch: `claude/project-thread-uasfag`, the harness-pinned branch for this session, restarted from `origin/main` at `ba6912c1ad` after cut 4 merged. The branch names no issue, so the claim comment is the identity record. **Release freeze.** Sales paused merges at 03:57Z for a release. This PR is opened as a draft and is not readied, armed or queued until the coordinator says merges resume. **Serial with the file's in-flight PRs.** Measured just before this PR was opened, on main `a57643d3de`. None of the 11 open PRs touches `protocol.ts`, `protocol-lock-version-gates.ts` or the bracketed-refusal pin (each PR's diff read against its merge base), and `git merge-tree` of this head with each of them, and with main, is clean. `origin/main` has not touched `packages/metadata-protocol` since this branch's base `ba6912c1ad`. ## What moves | Method | Base line | Tokens | |---|---|---| | `getEffectiveLock` | `:17113` | 115 | | `readLockGateOverlayLayer` | `:17150` | 66 | | `recordMetadataAudit` | `:17259` | 230 | | `lockWriteRefusal` | `:17338` | 263 | | `assertLockAllowsWrite` | `:17396` | 84 | | `assertLockAllowsDelete` | `:17416` | 242 | | `storedHashDigest` | `:17520` | 42 | | `receiptVersion` | `:17532` | 21 | | `storedHeadAt` | `:17559` | 68 | | `readVersionToken` | `:17598` | 175 | | `packagelessReadAddress` | `:17635` | 46 | | `strictDraftReadAddress` | `:17661` | 82 | | `packagelessDraftAddress` | `:17798` | 256 | | `storedParentForToken` | `:17839` | 86 | | `metadataConflictRefusal` | `:17879` | 209 | | `recordPendingDenialAudit` | `:17966` | 73 | All sixteen are `private`, so none is on the series' skip list of public members with inferred return types. **Stay in the class**, beside the moved methods: - **`recordOptimisticConflictAudit` and the static `optimisticConflictAuditEntry`.** The first reads a private static, which a module function cannot reach (TS2341; ruling `6364006498` precedent). - **`packagelessAddress`**, narrowed out during the trial, before the claim. - `scripts/check-durability-degradation-log-level.mjs`'s read-seam rule follows its `findEnvironmentRows` read. It also follows the four callers that guard it inside `protocol.ts`: `applyObjectRegistryMutation`, `deleteMetaItem`, `deletePackage` and `loadMetaFromDb`. - Moved, the rule's census dropped from 83 read seams to 79. Kept, the seam list is identical at both ends. The moved bodies call it through the protocol, as a host closure. ## The shape (#23050 shape B, as cuts 1–4 apply it) - **The class keeps every method** with its name, docblock, signature and position, less `async`. Each body is now one host literal plus `fn.call(host, …)`, marked `[#25467 cut 5]`. - **Typing.** `LockVersionGateHost` types every member as the class member itself (`ObjectStackProtocolImplementation['m']`). Each moved function types its `this` as the `Pick` of the host over exactly the members its body reads, measured by AST. Each stub annotates its literal as `ThisParameterType<typeof fn>`. There is no cast, and no member becomes public or protected. - **Members read**, measured by AST: - The field `engine`, as a getter, so each read happens when the body makes it. - These methods, as late-bound closures (`(...a) => this.m(...a)`): `artifactLockLayerAt`, `getEffectiveLock`, `getOverlayRepo`, `lockWriteRefusal`, `overlayLockLayerAt`, `packagelessAddress`, `packagelessDraftAddress`, `readLockGateOverlayLayer`, `receiptVersion`, `recordMetadataAudit`, `rethrowUnlessMetadataStoreUnprovisioned`, `storedHashDigest` and `storedHeadAt`. A spy or double set on the instance (for example `protocol.delete-rewrap-envelope.test.ts`'s wrapper around `assertLockAllowsDelete`) is still the one the body reaches. - No field is assigned, and the host carries no elevation member. The bodies that read as the system build their own `{ isSystem: true }` context, exactly as before. ## Module-level declarations, by reader Each declaration the moved bodies read was checked by an AST walk over the whole of base `protocol.ts`. The walk finds every identifier reference outside the declaration itself and takes the transitive closure over the moved set. **Move with the bodies, byte-identical and unexported.** Each has 0 readers outside the moved code and carries exactly its own docblock: | Declaration | Base line | Readers | |---|---|---| | `InboundVersionConflictError` (class) | `:3679` | `storedParentForToken` (throws it), `metadataConflictRefusal` (`instanceof`). One stub docblock that stays names it in `{@link}` prose. | | `ErrorWithPendingAudit` (type) | `:6192` | moved bodies only | There is still exactly one class, so the `instanceof` check meets the same constructor the throw uses. **Stay in `protocol.ts` and gain `export` in place**, the only edit to each: | Declaration | Read by code that stays | |---|---| | `canonicalMetaType` (`:417`) | `canonicalizeMetaRequestType`, `governServedItem`, `stripServedSystemColumns`, `bundleMemberOf` | | `MetadataAuditEntry` (`:6164`) | `withPendingAudit`, `optimisticConflictAuditEntry` | `src/index.ts` exports from `./protocol.js` by name, so neither is published (measured below). No import of `protocol.ts` loses its last reader. `protocol.ts` gains one statement, the import of the new module. ## The published `.d.ts` Under the series bar (ruling `6348891160`), measured last, on the final layout, at base `ba6912c1ad` and at head. This is the first cut that moves a class and a type, and the seat asked for this reading before the claim. Both ends were built with `pnpm --filter @objectstack/metadata-protocol build` against the same dependency tree (`turbo run build --filter='@objectstack/metadata-protocol^...'` at base first). - **Raw sha256** of `dist/index.d.ts` and `dist/index.d.cts`: `25da084d43455d125bfd41b85b1e70477ef7caf77f2cb7da23143b325e51a14e` at base and at head, both files, 770,690 bytes each. That is **raw identity**: - neither moved declaration prints anywhere new; - `canonicalMetaType`'s docblock does not splice onto a neighbour once it gains `export`; - the one stub docblock that names `InboundVersionConflictError` in `{@link}` prose prints byte-identical. - **Normalised sha256**, base and head: `ff7d6892b9f1161da356208d51301816c48dc021051544b9c28a3aa14329d57e`. The normaliser, run from `packages/metadata-protocol`: ```sh node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum ``` - **Positive control** on a copy of the head `dist/index.d.ts`: making line 1361, `private engine;`, public (`engine;`) gives raw `4bea7ae0…` and normalised `be016197…`. Both differ from the real hashes. - **Export tables.** The ESM and CJS bundles each export the same 78 names at base and head, loaded at runtime (`import()` and `require` of the built bundles). The sorted list hashes `e47ac7c753bcb8bee1e79c1a5f79f7394cf030bd526de81ea8bcbfcb71fa024a` on all four, as at cuts 1–4. None of `canonicalMetaType`, `MetadataAuditEntry` or `InboundVersionConflictError` appears. No changeset: `skip-changeset`, per ruling `6348891160`. ## Proof of move-only - **Bodies and declarations, by AST token** (recursive `getChildren` leaves). For each moved function, the parameters (less the added `this`), return type and body have the same token sequence as the base method. Each moved declaration has the same tokens, and its own docblock is byte-identical. 2,139 tokens compared, 0 differ. Negative control: one changed token makes the check report a difference. - **Class side.** The class has 299 members at base and at head. The 283 that did not move are byte-identical to base, and each of the 16 stubs keeps its docblock and signature byte-identical, less `async`. - **Top level of `protocol.ts`.** 240 of the 244 base statements other than the class are byte-identical with their leading comments. The other 4 are the 2 moved declarations and the 2 that gain `export`. The one new statement is the import of the new module. - **Size.** +674 / −294 over 3 files, 968 changed lines. `protocol.ts` goes from 30,718 to 30,548 lines, and `protocol-lock-version-gates.ts` has 547. ## Readers of the moved lines - **Bracketed-refusal pin** (`protocol.bracketed-refusal-opener-absence.test.ts`, `PRODUCERS`). 4 `new Error(` sites move, so `protocol.ts` goes 87 → 83 against its floor of 5. The new module is listed with its measured count as its floor, `'protocol-lock-version-gates.ts': 4`, as cut 3 listed its module. That is the only edit outside the move. No moved site opens with a bracketed tag. - **Durability checker** (`check-durability-degradation-log-level`). It gives identical output at both ends, with `packagelessAddress` kept (above). - **ADR anchors and decision letters.** `check:adr-anchors`: 84 → 84 anchored files. Decision-letter citations on the anchored surface go **1,883 → 1,882**, measured exactly at both ends. - The one that leaves is an `ADR-0110 D3` comment citation in `readLockGateOverlayLayer`'s body. - `protocol.ts`'s anchor entry governs ADR-0005/0029/0087/0119/0126. None of its paragraphs describes these bodies, so the new module gets no entry. - **Identical output at both ends:** `check-adr-symbol-anchors`, `check-platform-checklist` (`protocol.ts#` anchors on the moved methods resolve on the shells, which stay; not edited), `check-system-context-census`, `check-tenant-audit-census`, `check-query-options-erasure-ratchet` (no counted site moves; no file key added), and spec `check-liveness`. - **Engine-double contract.** `check-engine-double-contract` differs only in the line numbers of the `updateData`/`deleteData` seams in `protocol.ts`, plus `productionFiles` +1 (the new file). - **Governed surface.** `check-governed-merges --branch` on the final 3-path list: NOT governed. 968 changed lines, under the 3,000-line human-merge bar. - **Path readers.** Files that name `protocol.ts` next to a moved method name (`protocol.delete-rewrap-envelope.test.ts`, `protocol.lifecycle-audit-rows.test.ts`, `sys-metadata-repository.package-writability.test.ts`, objectql `protocol-publish-canonical-fold.test.ts`, the durability checker's comment table) name it in prose or spy on the instance. None reads the moved lines by path, and all pass. ## Verification - `tsc --noEmit` (package) exit 0. `eslint` on `protocol.ts` and `protocol-lock-version-gates.ts` exit 0. - **`packages/metadata-protocol` suite**, base and head, compared per test name and status. Base `ba6912c1ad`: 21,489 tests (21,470 passed, 0 failed, 19 skipped), 266 files. Head: 21,490 (21,471 passed, 0 failed, 19 skipped), 266 files. None lost, none changed status, one added: the new `PRODUCERS` row, `protocol-lock-version-gates.ts raises no message opening with a bracketed lowercase tag` (passed). - **Tests outside the package that read `protocol.ts` by path or name these methods**, run at head, all pass: - `core` `row-serving-door-exposure.pin` and `second-object-read-exposure.pin` (7, at both ends); - `objectql` `core-boundary.ratchet` and `protocol-publish-canonical-fold` (12); - `spec` `metadata-type-registry-allow-org-override-absence` and `scripts/liveness/evidence` (44); - `client` and `rest` `discovery-double-retired-key` (3 + 2). - **Hand gates at base and head**, all exit 0, identical output except the letter count above: `check-adr-anchors`, `check-adr-symbol-anchors`, `check-platform-checklist`, `check-system-context-census`, `check-tenant-audit-census`, `check-query-options-erasure-ratchet`, `check-durability-degradation-log-level`, and spec `check-liveness`. - **Derived gates.** `dispatch-gates --commands --changed` derives 59 gates on the final tree (3 changed paths). All 59 exit 0 at head, and `--ran` reconciles 59 of 59 with 0 NOT-MEASURED. `check-adr-symbol-anchors`, `check-liveness` and the durability checker's full run are not derived and were run by hand (above). The full list and the `--ran` reconcile are in the report on this PR. ## Carry-forward These stay in `protocol.ts` in this series: - **Public members whose return type is inferred.** Annotating the type would be a signature edit and needs a ruling. Later cuts skip `getDiscovery`, `getMetaTypes`, `getMetaItems`, `getMetaItemsForExecution`, `getMetaItem`, `getMetaItemForExecution`, `getUiView`, `findData`, `getData`, `validateData`, `createData`, `cloneData`, `updateData`, `deleteData` and `saveMetaItem`. - **Bodies kept by a reader:** - `probeRecord` (per-file erasure-baseline site); - `getMetaItemCached` (the #9157 pin reads its body by path); - `packagelessAddress` (the durability read-seam census, this cut). - **Private-static readers.** The inferred-return `.d.ts` still depends on `protocol.ts` byte offsets (cut 3's series note), so each cut measures it last. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8pUyVs5eas1AjMdReJM7C Co-authored-by: Claude <noreply@anthropic.com>
1 parent 10b8250 commit 60d8fd2

3 files changed

Lines changed: 674 additions & 294 deletions

File tree

0 commit comments

Comments
 (0)