Repository navigation
Commit bf5855b
<!-- 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 1 of the `protocol.ts` split (#24586 ruling A, recorded in
`6270097840`): the card's "batch and `*Many` data" section, the seat's
suggested first slice (1,067 lines, 33 of them from the last 30 days).
The claim is `6368300459`. Sixteen `ObjectStackProtocolImplementation`
method bodies move unedited into a new module,
`packages/metadata-protocol/src/protocol-batch-data.ts`:
- public: `batchData` (with its ADR-0119 atomic path), `createManyData`,
`insertManyData`, `updateManyData`, `deleteManyData`;
- private: `runAtomicBatchData`, `runAtomicBatch`, `runBatchDataLoop`,
`reconcileStoppedBatch`, `locateBatchCause`, `buildBatchDataResponse`,
`buildRolledBackBatchResponse`, `runUpdateManyLoop`,
`buildUpdateManyResponse`, `runDeleteManyLoop`,
`buildDeleteManyResponse`.
`triggerAutomation` sits between them in the class but is not batch
code, and stays.
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, cut from `origin/main` at `ab510f42e7` (the claim's
`ab510f4c43` is a typo for this commit). It names no issue, so the claim
comment is the identity record.
**Serial with the file's in-flight PRs.** At claim, none of the 21 open
PRs touched `protocol.ts` (each PR's file list read against
`origin/main`; #25451 touches only
`protocol.runtime-authoring-gate.test.ts`). Re-measured just before this
PR was opened, on all 18 open PRs: none touches `protocol.ts` (the
Version Packages PR only `CHANGELOG.md` and `package.json` in this
package).
## The shape (#23050 shape B, as #25222, #25223 and #25224 apply it)
- The class keeps each method with its name, signature, docblock and
position, because tsup emits members with their JSDoc into the `.d.ts`.
Each body is now one host literal plus `fn.call(host, …)`, marked
`[#25467 cut 1]`; `locateBatchCause` reads no member and is called
directly. The `async` methods drop `async` and return the moved
function's promise.
- `BatchDataHost` is new. It 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), and the stub annotates its literal as
`ThisParameterType<typeof fn>`. No cast, and no member becomes public or
protected.
- Methods go in as late-bound closures (`(...a) => this.m(...a)`), so a
spy or double set on the instance is still the one a body reaches, and
the moved bodies that call one another (`batchData` →
`runAtomicBatchData` → `runAtomicBatch` → the loop → the builders) do so
through the protocol, as before. The members passed this way:
`assertObjectRegistered`, `assertRecordExists`, `probeRecord`, and the
moved methods themselves.
- The one field read, `engine`, goes as a getter, so each read happens
when the body makes it (tests replace it on a constructed protocol).
- The one caller of a moved method outside the moved code,
`applyPublishBackfill` (`this.updateManyData(`), still calls the class
method.
**Module-level declarations.**
- `mergeDroppedFieldEvents` and `rowOperationFailureFallback` are read
only by the moved bodies. They move with them, unedited, docblocks
included, and stay unexported.
- `byIdUpdateRecord`, `rowRequiredIdError`, `toRowApiError`, `type
BatchDataRowResult` and `type BatchDataLoopOutcome` are also read by
code that stays, so they stay and gain `export` in place, the only edit
to each. The module imports them back from `./protocol.js`.
- `src/index.ts` exports from `./protocol.js` by name, so none of them
is published. Measured below: the `.d.ts` and the export lists do not
move.
- Each value import is read only inside a function body, at call time,
never while either module evaluates, so the module cycle is
evaluation-safe.
- The module imports `engineCanRollBack` and `recordNotFoundError`
(`@objectstack/core`), `omitInternalFieldsFromWriteResponse`
(`./write-response-internal-fields.js`) and the spec types the bodies
name, from the same specifiers `protocol.ts` uses. `protocol.ts` keeps
all its imports (each still has readers there).
## The published `.d.ts` does not change
Under the series bar (ruling `6348891160`), measured on base
`ab510f42e7` and on the head, both built against the same dependency
tree (a full `turbo run build` at base, 77/77 tasks, then `pnpm --filter
@objectstack/metadata-protocol build` at head):
- Raw sha256 of `dist/index.d.ts` and `dist/index.d.cts`, base and head,
all four the same:
`8e1a224c7d5a800c65c0174f9bc2245b70e0bab2106ab8dbf8a4b2dedd4ca35f`. The
raw diff is 0 lines.
- Normalised sha256, base and head the same:
`5b98a8e857554959f7561563d6a0742110c5d3d8ad6119065ed5a69b0bc06f95`. 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`: line 1348,
`private engine;`, made public (`engine;`) gives raw
`66d80e4206a14b2b7e76dc37496d347d9dd39f53c66d016966844e707992e5ac` and
normalised
`f40390e5c426e0c6e24523cd6908726d3f3cab87f9a4ac36df20992bec04da95`, both
different 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.
No changeset: `skip-changeset`, per ruling `6348891160`.
## Proof of move-only
- **Bodies, by AST token** (recursive `getChildren` leaves, so template
literals scan correctly): each moved function's parameters (less the
added `this`), return type and body have the same token sequence as the
base method, and each body line is the base line with four leading
spaces removed. `async` matches on all sixteen. The two moved
declarations are token-identical and their docblocks byte-identical.
3,982 tokens compared, 0 differ. Negative control: one changed token in
`batchData` makes the check report a difference.
- **Class side**: the class has 298 members at base and head. The 282
members that did not move are byte-identical to base. Each of the 16
stubs keeps its docblock and signature byte-identical, less `async`.
- **Top level of `protocol.ts`**: 249 of the 256 base statements other
than the class are byte-identical. The others: the five `export`
additions above, and the two declarations that moved. The one new
statement is the import of the new module.
- **Size**: +1,093 / -735 over 4 files, under the 3,000-line
manual-merge line. `protocol.ts` goes from 33,065 to 32,468 lines (each
stub keeps its docblock and carries its host literal, so the file
shrinks by less than the bodies' length); `protocol-batch-data.ts` has
944.
## Readers of the moved lines, migrated in this PR
- **ADR anchors.** The `protocol.ts` entry in `scripts/adr-anchors/`
carries an ADR-0119 paragraph that describes `batchData`'s atomic path,
which now lives in the module. As in the #25223 and #25224 cuts, the
module gets its own entry
(`packages__metadata-protocol__src__protocol-batch-data.ts.json`)
carrying ADR-0119 and that paragraph verbatim. The `protocol.ts` entry
is unchanged, and ADR-0119 is still cited in `protocol.ts` outside the
moved text. `check:adr-anchors`: 80 → 81 anchored files, decision-letter
citations on the anchored surface 2,001 → 2,001.
- **`protocol.bracketed-refusal-opener-absence.test.ts`.** It scans each
producer file's source for refusals opening with a bracketed tag, with a
per-file `new Error(` floor so a scan that finds nothing cannot pass for
free. The moved text holds 4 `new Error(` sites, so the module joins
`PRODUCERS` with a floor of 4. Without it those throw sites would have
left the scan silently. `protocol.ts` keeps its floor of 5.
- Read and not edited:
- `docs/adr/0119-…md` anchors `protocol.ts#batchData` twice. The method
shell stays, so it still resolves (`check-adr-symbol-anchors` at both
ends below).
- `protocol.write-response-internal-fields.tripwire.test.ts` enumerates
`*Data` methods on the prototype at runtime; the stubs keep every name.
- `protocol.read-verb-canonical-fold.test.ts` and the other source scans
that read `./protocol.ts` (`register-item`, `lookup-artifact-item` and
`view-expansion-reconcile` call-site pins, `query-transport-dialect`,
`object-registry-write-through-spelling`,
`hydrate-overlay-canonical-type`, the two recovery-door pins): none of
their patterns occurs in the moved text, and all pass at head.
- `scripts/engine-double-contract.seams.json` names `protocol.ts` rows
for `deleteData` and `updateData` only, which stay.
- `scripts/query-options-erasure-baseline.json` counts `protocol.ts` at
6: the moved text has no engine `find` / `findOne` / `count` /
`aggregate` call, so no counted site moves.
- No `isSystem` read sits in the moved text (system-context census).
- The tenant-audit census places no `metadata-protocol` write site (238
sites at base and head, 0 in this package); it exits 0 at both ends with
no `--write`.
## Verification
- `tsc --noEmit` (package) exit 0; `eslint` on both source files exit 0.
- `packages/metadata-protocol` suite: base 21,466 tests (21,447 passed,
0 failed, 19 skipped, 1,181 suites); head 21,467 (21,448 passed, 0
failed, 19 skipped, 1,181 suites). Compared per test name and status: no
test lost, none changed status; the one added test is the new
`PRODUCERS` row's case.
- Tests outside the package that read `protocol.ts` by path, run at
head, all pass: `core` `row-serving-door-exposure.pin` and
`second-object-read-exposure.pin` (7), `objectql`
`core-boundary.ratchet` (2), `spec`
`metadata-type-registry-allow-org-override-absence` (2) and
`scripts/liveness/evidence` (42), `client` and `rest`
`discovery-double-retired-key` (3 + 2); `spec check:liveness` passes.
- Hand gates at base and head, all exit 0 at both:
`check-adr-symbol-anchors` (2,384 anchors resolve at both),
`check-adr-anchors`, `check-tenant-audit-census`,
`check-platform-checklist`.
- **After merging `origin/main` at `ea33919f56`** (#25451 had added a
test file in this package; nothing else in the package moved): rebuilt
the package closure (13/13), package `tsc` exit 0, suite 21,476 tests
(21,457 passed, 0 failed, 19 skipped, 1,182 suites; the 10 added are
#25451's file and the new `PRODUCERS` case), `dist/index.d.ts` still
`8e1a224c…`.
- `dispatch-gates --commands --changed`: 67 derived gates, each run at
head with its exit code; the full list and the `--ran` reconcile are in
the report on this PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J8pUyVs5eas1AjMdReJM7C
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6755432 commit bf5855b
4 files changed
Lines changed: 1093 additions & 735 deletions
File tree
- packages/metadata-protocol/src
- scripts/adr-anchors
0 commit comments