Repository navigation
Commit 59d9181
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DMYdQaNcHT9ZVNXcm3pkCpX)_
Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
Cut 4 of the `packages/objectql/src/engine.ts` split (#24586 ruling A,
recorded in `6270097840`). The claim is `6408069065`. The bodies of the
two private `ObjectQL` realtime publish methods move unedited into a new
module, `packages/objectql/src/engine-realtime-publish.ts`:
`publishDataEvent` and `publishBulkDataEvent`. The ten module-level
event helpers that only those two call move with them, byte for byte.
#25522 stays open for the later cuts.
Branch `claude/project-thread-6ikbqy` (platform-assigned to this card
thread), cut from `origin/main` at `f00e8d5c8`. Since then `main` has
moved to `2a728ba2a`; no commit in that range touches
`packages/objectql/**` or the docs page this PR edits. The branch has
not been merged forward, and CI builds the merge ref.
**Serial with the file's in-flight PRs (A1).** Before the first edit,
none of the 7 open PRs touched `packages/objectql/src/engine.ts`,
`engine-data-events.test.ts`,
`federated-injected-column-readers.test.ts` or `realtime-protocol.mdx`
(every file list read through the REST API). Seat 1's #25653 edits
`withRunAs` in `engine.ts`, a different region. Just before this PR
opened I measured again: 16 PRs are open, and one new one touches
`engine.ts`. #25935 (opened 04:48Z, after this claim) adds one import
line near base line 279 and an aggregate refusal near base line 21954.
Neither overlaps this diff; the nearest hunk here ends at base line 263.
For each of those PRs, whichever lands second merges `main`.
## What moves, re-measured on `origin/main` `f00e8d5c8`
The cut-3 dev proposed this area in PR #25686's body. I measured it
again before moving anything:
- **Both methods are private with explicit return types** (a `Promise`
of `void`). The stubs keep both signatures and docblocks, so the `.d.ts`
keeps its bytes (measured below).
- **18 lines from the last 30 days** in the two bodies (16 in
`publishDataEvent`, 2 in `publishBulkDataEvent`; `git blame
--since=2026-09-26T04:00Z` at `f00e8d5c8`, after deepening history to
2026-07-19). The moved helpers carry 71 recent lines out of 279.
Control: the whole file has 7,261 recent lines on the same blame.
- **No `this.FIELD.delete/insert/update(` site and no static read.** The
bodies read three members only: `realtimeService`, `logger` and
`_registry`.
- **No spy and no instance double** on either method or on
`realtimeService` anywhere in the repo's tests. The callers still call
the methods on the engine.
- **The helpers.** The bodies call ten module-level functions in
`engine.ts`: `generateEventUuid`, `eventRecordId`, `eventRecordBody`,
`redactEventMetadataBody`, `projectEventRecordBody`, `eventUserId`,
`eventOrganizationId`, `eventOrganizationValue`,
`bulkEventOrganizationId` and `eventMatchedCount`. No other code in
`engine.ts` calls any of them, measured by name. They move with the
bodies, so the new module needs no runtime import of `engine.ts`.
`accessibleOrganizationsOf` sat between them, but the tenant wall reads
it, so it stays.
The cut-3 proposal priced this cut at about −95 lines and listed no path
reader. Both readings are corrected here. The helpers move too, so the
cut is larger. And three readers read the moved code by path; each is
migrated below.
## The shape (#23050 shape B, as cuts 1 to 3 apply it)
- The class keeps each method with its name, signature, docblock and
position. Each body is now one host literal plus `fn.call(host, …)`,
marked `[#25522 cut 4]`.
- `RealtimePublishHost` is new. It types each member as the class member
itself (`ObjectQL['realtimeService']`, `ObjectQL['logger']`,
`ObjectQL['_registry']`). Each moved function types its `this` as the
`Pick` of the host over exactly the members its body reads, and each
stub annotates its literal as the `ThisParameterType` of the moved
function. There is no cast, and no member becomes public or protected.
- **All three members go as getters.** Each read happens when the body
makes it, so a service installed later by `setRealtimeService` is seen
as before. `publishBulkDataEvent`'s host has no `_registry`, because its
body never reads it.
- **Imports.** The module imports `ObjectQL` as a type only, so there is
no module cycle at run time. It imports what the moved code reads, with
the specifiers `engine.ts` used. `engine.ts` drops these imported names,
whose only reader was the moved code: `redactStoredMetadataBody`,
`STORED_METADATA_BODY_COLUMN` and `STORED_METADATA_TYPE_COLUMN`
(`@objectstack/spec/kernel`), `RealtimeEventPayload`
(`@objectstack/spec/contracts`), the whole `@objectstack/spec/api`
import (`BulkDataEventSchema`, `DataEventSchema`, `BulkDataEvent`,
`DataEvent`), and `omitInternalFieldsFromWriteResponse`
(`@objectstack/core`; its three other mentions are prose). The
`[#21120]` and `omitInternalFieldsFromWriteResponse` import comments
move with their imports. Nothing re-exports the module.
## Net lines (the seat's rule: an area moves only when `engine.ts`
shrinks)
| | base `f00e8d5c8` | head |
|---|---|---|
| `engine.ts` (`wc -l`) | 23,820 | 23,430 (−390) |
| `engine-realtime-publish.ts` | — | 507 |
Diff: +538 / −420 over 5 files, under the 3,000-line manual-merge line.
## The published `.d.ts` does not change (ruling `6348891160`)
Base: the `@objectstack/objectql` dependency closure built at this
branch (`pnpm --filter '@objectstack/objectql^...' build`; the diff
touches none of it). Then `pnpm --filter @objectstack/objectql build`
with `engine.ts` restored from `f00e8d5c8` (tree only), and the base
`dist/` saved. The head build runs after `git checkout HEAD --
packages/objectql/src/engine.ts`, with `git status` clean. The head
`dist/index.mjs` carries `readRealtimeService` 4 times and the base copy
0 times, so each build is the one it claims to be. I ran the pair twice
(once for the declarations, once for the export lists), with the same
result.
All six declaration files are **byte-identical**, and the raw diff of
each is 0 lines. The util chunk name, `util-BuVJ7CNx`, is the same at
both ends.
| file | raw sha256, base = head |
|---|---|
| `index.d.ts` |
`6f76e7978f0fc72c6bbcf129f97c753188123ed605cf01ee38d1774ea8f0e69a` |
| `index.d.mts` |
`21fdbb330b86ada1f46e2431bae853a8ccb8d64f08469e3e2b41254a50605145` |
| `core.d.ts` |
`2cd3ea741f5c01f5de3274d5728254ead8bacb8dcd57886bede9f39c648bb2f1` |
| `core.d.mts` |
`47c1c62d395f5a63d64842bd99ecf6fbeadbf5a34c057b7a9f4b005377c11c5a` |
| `util-BuVJ7CNx.d.ts` and `.d.mts` |
`05835759410068e3f1002223baf35eca63064ee4d090b1eb5fd5276b2da3feee` |
- **Normaliser.** I ran the ruling's one-liner verbatim (double-quoted
unions) on every file. Normalised = raw at both ends, because this
package prints single-quoted literals. This cut does not depend on it:
the raw bytes are identical.
- **Positive control**, in the same run, on a copy of the head
`util-BuVJ7CNx.d.ts`. Line 7157 `private publishDataEvent;` made public
(`publishDataEvent;`, raw diff 4 lines) gives raw = normalised
`a379ca74f62d51b31185fa0119cfcd24e1a5c920645e135390c257e372f4192d`,
which differs from `05835759…`.
- **Export lists**, loaded at run time from the built bundles (ESM
`import()` and CJS `require`), are identical at base and head. `.` has
182 exports under ESM and 182 under CJS; `./core` has 54 and 54. The
sorted lists hash
`881616cc179189098182bd6b8a95701d9922f9677b786bc12e2d31c1adb0adaa` and
`393e806906e876c2e020aa96617d54050a35ae63cd483b67d8e968b7b5390f49` at
both ends, the same as cuts 2 and 3. No moved name is exported.
No changeset: `skip-changeset`, per ruling `6348891160` item 4.
## Proof of move-only
The proof uses AST tokens: the recursive `getChildren` leaves of the
TypeScript parse. JSDoc leaves are compared with leading indentation
stripped per line, because the moved code is one level shallower.
- **Bodies.** For each moved function, the parameters (without the added
`this`), the return type and the body have the same token sequence as
the base method: `publishDataEvent` 371 tokens and
`publishBulkDataEvent` 287 tokens, both SAME. Every body line is the
base line less two leading spaces (60 and 57 lines, braces included).
- **Helpers.** All ten are byte-identical to base, full text with the
docblock (`getFullText`, trimmed): 741 tokens in all.
- **Negative controls.** Changing one token in the moved
`publishBulkDataEvent` (`matched === 0` to `matched === 1`, in memory)
makes the same check report DIFFER. Changing one in the moved helper
`eventRecordId` also reports DIFFER.
- **Class side.** The class has 318 members at base and at head, in the
same order. The 316 that did not move are byte-identical (full text,
docblocks included). The 2 stubs keep their docblocks, modifiers and
signatures byte-identical.
- **Top level of `engine.ts`.** 222 of the 237 base statements are
byte-identical at head. The other 15 are the 10 helpers, the class, and
four imports: three narrowed by the names listed above, and the
`@objectstack/spec/api` import, gone whole. The new statements are the
import of the new module, the three narrowed imports, and the class.
- **The tests reach the moved code** through the stubs:
`engine-data-events.test.ts` (42 cases),
`engine-realtime-credential-mask.test.ts` and
`engine-realtime-stored-metadata-body.test.ts`. All pass at head
(below).
## Readers of the moved lines, migrated in this PR
- **`engine-data-events.test.ts`'s source pin**
("BulkDataEventSchema.parse at the publish site stays the validator…")
read `engine.ts` for `private async publishBulkDataEvent(` and `function
bulkEventOrganizationId(`. It now reads `engine-realtime-publish.ts` for
`export async function publishBulkDataEvent(`, and the body ends at the
module's own closing brace. Every assertion is unchanged. **Proof that
it can still fail:** `scripts/ablation-replace.mjs` added an
`eventOrganizationId(` call into the moved bulk body (anchor 1 → 0, blob
`02e17ff9f831` → `87ab8ae01da1`). The pin went red: 1 failed / 41
passed. Restored, the blob is HEAD's again and `git diff HEAD` is empty.
- **`federated-injected-column-readers.test.ts`** keys each seam use as
`FILE#FUNCTION :: SEAM`. The row for `eventOrganizationId ::
resolveTenantFieldName()` moves from `engine.ts#` to
`engine-realtime-publish.ts#`. Its disposition and reason are unchanged.
**Proof:** putting the old key back (blob `17bd6adaf9c5` →
`6ea31c760521`) makes "has a row for every seam use in the source" fail
and name the new key as unlisted. Restored the same way.
- **`content/docs/protocol/kernel/realtime-protocol.mdx`** pointed at
`publishDataEvent` in `packages/objectql/src/engine.ts` for what
`changes` carries. It now names `engine-realtime-publish.ts`.
- **Liveness ledgers.** No ledger names a moved symbol; `pnpm --filter
@objectstack/spec check:liveness` exits 0 at head.
- **ADR anchors (A2).**
`scripts/adr-anchors/packages__objectql__src__engine.ts.json`'s one
paragraph is about the transaction seam, which does not move. No anchor
entry describes a moved body, so none is added. `check:adr-anchors`
exits 0 at both ends: 137 decision numbers, and 58,841 citations at both
ends (8,253 files at base, 8,254 at head: the new module). **The
decision-letter count on the anchored surface goes from 1,847 at base to
1,842 at head.** The five citations are ADR-0076 D2, ADR-0131 D8,
ADR-0095 D1, ADR-0105 D2 and ADR-0131 D7. They sit in the moved helper
docblocks, and the new module is not an anchored file. They are still
valid letters. A probe entry for the new module, made only to measure
and then deleted, put the count back to 1,847 with the gate at exit 0.
Whether the module should get its own entry is in Acceptance notes.
- **Other path readers.** `check-adr-symbol-anchors`: 2,474 anchors
resolve. `check-platform-checklist --changed-from origin/main`: 1
anchored file in the change set, 14 anchors judged, 0 lost.
## Direct dependants (A3, narrowed and declared)
No dependant was type-checked locally. Both moved methods are private,
so no dependant can reach them, and the six published declaration files
are byte-identical. CI's TypeScript Type Check lanes cover the
dependants.
## For the next dispatch (cut 5)
From cut 3's list, the areas still waiting are these: Roll-up summaries
(spec-coupled, see cut 1); the Datasource registry (U = 3);
Related-record expansion (`expandRelatedRecords` reads the private
static `MAX_EXPAND_DEPTH`). I did not measure a fresh seam table for cut
5. The next dispatch should measure one.
## Acceptance notes
- **An ADR anchor entry for `engine-realtime-publish.ts`?** Five
decision-letter citations left the anchored surface with the moved
helpers (above). By card ask 3, no entry is owed, because no anchor
paragraph describes a moved body. This claim's file surface also does
not include `scripts/adr-anchors/**`, so I did not add one. If the seat
wants those five letters checked again, a one-file entry that names
ADR-0131 would do it. The paragraph would cover
`bulkEventOrganizationId`, which reads the recorded wall verdict alone
(D8). The probe above shows that entry passes. Carrier: the seat.
## Verification
All at head `2108463930`.
- `pnpm --filter @objectstack/objectql typecheck` (`tsc --noEmit` and
`check:test-typecheck`), under `os-verify-lock`: VERDICT command-exit 0.
39 files / 233 errors / 64 pinned signatures held.
- `packages/objectql` suite (`vitest run --project local
--maxWorkers=2`, under `os-verify-lock`, VERDICT command-exit 0): 467
files, 1,903 suites, 8,718 tests, 8,718 passed, 0 failed, 0 skipped.
- `pnpm lint` (`eslint . --no-inline-config`, repo-wide, as the seat
asked): exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 91 families on the real diff at
`2108463930` (stderr names the repo and the commit). All 91 were run,
and each exit code was recorded before any pipe. 89 exited 0 on the
first pass. `check:skill-examples` exited 3 until I built the
`@objectstack/client-react...` closure, then exited 0 (262 prose
examples type-check). `check-engine-split-ratio --days 90` exited 0,
ratio 97.8%, with history deepened to 2026-07-19. `--ran` reconciles: 91
accounted for, 90 run, 1 NOT MEASURED:
- `check:dual-build-cjs-loads` exited 3, PREREQUISITE NOT MET: the other
packages have no `dist/`. The export lists above answer its question for
this package, and CI runs it.
- Hand gates, all exit 0: `check-adr-anchors` (base and head),
`check-adr-symbol-anchors`, `check-platform-checklist --changed-from
origin/main`, `check-commit-card-trailers --range origin/main..HEAD`.
- `test:repo` was not run locally; CI's `Test Core` runs it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017kEHq65soeg6DRE9DDutT2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3b957f9 commit 59d9181
6 files changed
Lines changed: 545 additions & 420 deletions
File tree
- content/docs/protocol/kernel
- packages/objectql/src
- scripts/adr-anchors
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
432 | 432 | | |
433 | 433 | | |
434 | 434 | | |
435 | | - | |
| 435 | + | |
436 | 436 | | |
437 | 437 | | |
438 | 438 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1009 | 1009 | | |
1010 | 1010 | | |
1011 | 1011 | | |
1012 | | - | |
1013 | | - | |
1014 | | - | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
1015 | 1016 | | |
1016 | | - | |
| 1017 | + | |
1017 | 1018 | | |
1018 | 1019 | | |
1019 | 1020 | | |
| |||
0 commit comments