Repository navigation
Commit 60d8fd2
<!-- 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
- packages/metadata-protocol/src
0 commit comments