Skip to content

refactor(objectql): move the lifecycle health bodies out of engine.ts into engine-lifecycle-health.ts (#25522 cut 3) - #25686

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25522-objectql-engine-cut-3
Oct 25, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25522-objectql-engine-cut-3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160

Cut 3 of the packages/objectql/src/engine.ts split (#24586 ruling A, recorded in 6270097840). The claim is 6386147359. Two public ObjectQL method bodies move unedited into a new module, packages/objectql/src/engine-lifecycle-health.ts: checkDriversHealth and resolvePrimaryDatasource. init stays: its return type is inferred. #25522 stays open for the later cuts.

Branch claude/issue-25522-objectql-engine-cut-3, cut from origin/main at 24d350b45. Since then main has moved five commits (to b2909fd46). None touches packages/objectql/src/**; #25657 changes packages/objectql/package.json's zod range only. The branch has not been merged forward; CI builds the merge ref.

Serial with the file's in-flight PRs (A1). At start, none of the 14 open PRs touched packages/objectql/src/engine.ts (every file list read through the REST API, the 183-file Version Packages PR #25640 page by page). Just before this PR was opened I measured again: 10 PRs are open, and none touches it (the two new ones, #25682 and #25684, read file by file; the other 8 unchanged).

What moves, re-measured on origin/main 24d350b45

The cut-2 dev proposed this area in PR #25636's body. I measured it again before moving anything, and every premise holds:

  • Both are public with explicit return types: checkDriversHealth(opts?: { timeoutMs?: number }), which returns a Promise of DriverHealth[], and resolvePrimaryDatasource(): PrimaryDatasourceVerdict. The stubs keep both signatures, so the .d.ts keeps its bytes (measured below).
  • 0 lines from the last 30 days. git blame --since=2026-09-24T16:00Z over both methods, docblocks included (156 lines), finds 0 recent lines. Control on the same blame: the whole file has 8,293 recent lines. History was deepened to 2026-09-15 first (126 commits touch the file in 30 days, 35 in 7).
  • No this.FIELD.delete/insert/update( site, and no static read. PLATFORM_PROVIDED_OBJECT_NAMES is a module import from @objectstack/spec/system, not a class static. It has no other reader in engine.ts, so it leaves engine.ts's import and the new module imports it.
  • No engine.ts#… path reader for either symbol anywhere in the repo (packages/spec/liveness/**, scripts/**, docs/**, content/**).
  • No spy and no instance double on a real engine. The two names appear in tests only as direct calls on a real engine (engine-driver-health.test.ts, engine-primary-datasource.test.ts, engine-driver-eviction.test.ts, datasource-unavailable.test.ts) or as whole-engine fakes (packages/runtime/src/http-dispatcher.ready.test.ts), which never reach ObjectQL.

The shape (#23050 shape B, as cuts 1 and 2 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 3].
  • LifecycleHealthHost is new. It types each member as the class member itself (ObjectQL['drivers'], ObjectQL['_registry'], ObjectQL['isSystemLedgerObject'], ObjectQL['resolveDatasourceBinding']). 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.
  • Fields go as getters: drivers (both bodies) and _registry (resolvePrimaryDatasource). Each read happens when the body makes it, so a driver registered or evicted later is seen as before.
  • Methods go as late-bound closures: isSystemLedgerObject and resolveDatasourceBinding. A double set on the instance is still the one the body calls.
  • checkDriversHealth stays async on both the stub and the moved function, so a throw inside the body is still a rejected promise.
  • Imports. The module imports ObjectQL as a type only, so there is no module cycle at run time. It imports DriverHealth and PrimaryDatasourceVerdict as types from ./driver-connect-errors.js and PLATFORM_PROVIDED_OBJECT_NAMES from @objectstack/spec/system, the specifiers engine.ts uses. engine.ts gains one import and drops PLATFORM_PROVIDED_OBJECT_NAMES from its @objectstack/spec/system import, its only other reader being a backticked docblock mention. Nothing re-exports the module.

Net lines (this seat's rule: an area moves only when engine.ts shrinks)

base 24d350b45 head
engine.ts (wc -l) 23,807 23,755 (−52)
engine-lifecycle-health.ts — 143

Diff: +163 / −72 over 2 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, which the diff does not touch), then pnpm --filter @objectstack/objectql build with engine.ts restored from 24d350b45 (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 readRegistry twice and the base copy 0 times, so each build is the one it claims to be.

All six declaration files are byte-identical. The raw diff of each is 0 lines. The util chunk name, util-ByiLm3B6, is the same at both ends.

file raw sha256, base = head normalised (ruling, verbatim), base = head normalised (single-quoted spelling), base = head
index.d.ts 9ca22071717e3f761198115fa54a143df4151a52950d826a10677420dcad16ae 9ca22071717e… c15106196eb2…
index.d.mts 69dd1cfe3875024f434b2d7defce771ebc8c7e48a05be7c946fa6ecdbd912b40 69dd1cfe3875… a2d6d7b241e8…
core.d.ts d27b72bde2eec9a47c7dd3c104976321533cf397452ca9f84491a455ec28e73b d27b72bde2ee… d27b72bde2ee…
core.d.mts 5a0cca505296b678b31c742c285e028fe434f5467bb8d27e052e470ce0e09652 5a0cca505296… 5a0cca505296…
util-ByiLm3B6.d.ts and .d.mts 1742a9cb029654d1ca4e8e392e98001eec85963987f516b1b76f672ecd08e5a9 1742a9cb0296… bffd1b630dab…
  • Normaliser, the ruling's one-liner verbatim (double-quoted unions): 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(" | ")))' FILE | sha256sum. This package prints single-quoted literals, so on these files it changes nothing and normalised = raw. The third column is the same single class spelled with a single quote; it also matches. This cut does not depend on it, because the raw bytes are identical.
  • Positive control, in the same run, on a copy of the head util-ByiLm3B6.d.ts: line 8318 private isSystemLedgerObject; made public (isSystemLedgerObject;, raw diff 2 lines) gives raw = ruled-normalised c944677ee7e7c23a9ecabb6466a616a9f993d3e41fbee3b854527e039c7d19cd, which differs from 1742a9cb…. The single-quote normalisation gives e86dbb567ef0…, which differs from bffd1b630dab….
  • 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 cut 2's).

No changeset: skip-changeset, per ruling 6348891160 item 4.

Proof of move-only

  • Bodies, by AST token (recursive getChildren leaves of the TypeScript parse). For each moved function, the parameters (without the added this), the return type, the async modifier and the body have the same token sequence as the base method: checkDriversHealth 251 tokens, resolvePrimaryDatasource 218 tokens, all SAME. Every body line is the base line less two leading spaces (36 and 39 lines, braces included). Negative control: one changed token in the moved resolvePrimaryDatasource ('system-object-unbound' misspelled in memory) makes the same check report DIFFER.
  • Class side. The class has 317 members at base and at head. The 315 that did not move are byte-identical to base (full text, docblocks included). The 2 stubs keep their docblocks and signatures byte-identical.
  • Top level of engine.ts. 234 of the 235 base statements outside the class are byte-identical at head. The other is the @objectstack/spec/system import less PLATFORM_PROVIDED_OBJECT_NAMES. The one new statement is the import of the new module.
  • The tests reach the moved bodies through the stubs: engine-driver-health.test.ts (7 cases on checkDriversHealth, timeout and skip paths included), engine-primary-datasource.test.ts (every resolvePrimaryDatasource verdict), engine-driver-eviction.test.ts and datasource-unavailable.test.ts. All pass at head (below).

Readers of the moved lines

  • ADR anchors (A2). scripts/adr-anchors/packages__objectql__src__engine.ts.json's one paragraph is about the transaction seam (buildDriverOptions, enforceTransactionOrigin), neither of which moves. The ADR-0057 §3.6 mention in resolvePrimaryDatasource's docblock stays on the stub; the one-line // ADR-0057 §3.6 comment inside the body moves with it. No anchor entry describes a moved body, so none is added. check:adr-anchors exit 0 at both ends: 85 anchored files; 1,847 decision-letter citations on the anchored surface at base and at head; 52,546 citations at both ends (6,610 files at base, 6,611 at head: the new module).
  • Liveness ledgers. pnpm --filter @objectstack/spec check:liveness exits 0 at head. No ledger points at either moved symbol.
  • Other path readers. check-adr-symbol-anchors: 2,384 anchors resolve at head. check-platform-checklist --changed-from origin/main: 1 anchored file in the change set, 14 anchors judged, 0 lost.

Direct dependants (A3)

The moved members are reached from @objectstack/runtime (http-dispatcher.ts's /ready handler calls both) and named in @objectstack/service-datasource's comments. I built their dependency closures (pnpm --filter '@objectstack/runtime^...' --filter '@objectstack/service-datasource^...' build, under os-verify-lock, VERDICT command-exit 0) and ran pnpm --filter @objectstack/runtime --filter @objectstack/service-datasource typecheck (VERDICT command-exit 0): service-datasource tsc --noEmit done; runtime tsc --noEmit and check:test-typecheck OK; 0 error TS lines. Both read the published declarations, which are byte-identical above.

For the next dispatch (cut 4)

  • Proposed area: Realtime publish (the tail of the "Write guard and realtime publish" row): publishDataEvent (58-line body) and publishBulkDataEvent (55-line body).
    • Both private with explicit return types; no this.FIELD.delete/insert/update( site; no static read; no engine.ts#… path reader; no vi.spyOn or instance assignment on either, measured repo-wide.
    • 18 lines from the last 30 days (16 in publishDataEvent's range, 2 in publishBulkDataEvent's).
    • Host: three getters only (realtimeService, logger, _registry).
    • Priced against the net-line rule: 113 body lines out, about 2 × 7 stub lines and 3 import lines in, so engine.ts drops by roughly 95 lines.
  • Stays from that row: assertWriteAllowed. packages/spec/liveness/datasource.json cites packages/objectql/src/engine.ts#assertWriteAllowed as evidence, the same reason resolveDatasourceBinding stayed in cut 2. setRealtimeService and listUnavailableDatasources are 1–2-line bodies, shorter than a host literal.
  • Still waiting: Roll-up summaries (spec-coupled, see cut 1); the Datasource registry (U = 3); Related-record expansion (expandRelatedRecords reads the private static MAX_EXPAND_DEPTH).

Verification

All at head 7b61487ac.

  • 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): 466 files, 1,899 suites, 8,700 tests, 8,700 passed, 0 failed, 0 skipped.
  • eslint --no-inline-config packages/objectql: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 56 families on the real diff at 7b61487ac (stderr names the repo and the commit). All 56 were run, each exit code recorded before any pipe. 53 exit 0 on the first pass, and check-engine-split-ratio --days 90 exit 0 after the history deepening it names as its remedy (git fetch --shallow-since=2026-07-19; it had refused with exit 2, CANNOT COMPUTE; ratio 98.1%). --ran reconciles: 56 accounted for, 54 run, 2 NOT MEASURED. CI runs both:
    • check-plugin-teardown-shape --self-test exit 3: its pinned fixture commit is not in this shallow clone.
    • check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET: 54 export entries of other packages have no dist/ (9 listed, "… 45 more"). The export lists above answer its question for this package.
  • Hand gates, all exit 0: check-adr-anchors (base and head), check-adr-symbol-anchors, check-platform-checklist --changed-from origin/main, pnpm --filter @objectstack/spec check:liveness.
  • test:repo was not run locally; CI's Test Core runs it.

Generated by Claude Code

…rce bodies to engine-lifecycle-health.ts

Cut 3 of the engine.ts split: the two lifecycle health bodies move unedited
into a new module through LifecycleHealthHost (drivers and _registry as
getters, isSystemLedgerObject and resolveDatasourceBinding as late-bound
closures). The class keeps both methods with their signatures and docblocks.
init stays (inferred return type).

Claude-Session: https://claude.ai/code/session_0161WE4VX4T7VT66M6pYcVjD
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 8 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/external-datasources.mdx (via checkDriversHealth (symbol, a method of class ObjectQL; a top-level function))
  • content/docs/permissions/system-context.mdx (via isSystemLedgerObject (symbol, a field of interface LifecycleHealthHost), isSystemLedgerObject (literal, a string literal in isSystemLedgerObject; a string literal in resolvePrimaryDatasource))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b2909fd46e541f8a310023641320fef58f115091 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b2b133a326c33e499f0cce6082a24a9d307d4240 — the merge of head 7b61487ac1b5cc2d961aede3a3e8575d740e5c55 into base b2909fd46e541f8a310023641320fef58f115091, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b2b133a326c33e499f0cce6082a24a9d307d4240 && git checkout b2b133a326c33e499f0cce6082a24a9d307d4240
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b2909fd46e541f8a310023641320fef58f115091 7b61487ac1b5cc2d961aede3a3e8575d740e5c55 && git checkout -B drift-repro b2909fd46e541f8a310023641320fef58f115091 && git merge --no-ff 7b61487ac1b5cc2d961aede3a3e8575d740e5c55

node scripts/docs-audit/affected-docs.mjs --json b2909fd46e541f8a310023641320fef58f115091

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b2909fd46e541f8a310023641320fef58f115091 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · sunhaolin · session_01JVTd3PprStGa26aez5omLB · 2026-10-25T03:31Z · head 7b61487ac1 · claim 6386147359 · report 6386558265

  • Shape: draft, base main, author objectstack-fleet[bot], Part of #25522, no closing keyword, Clause-②: no, Metadata-protocol: none, skip-changeset (ruling 6348891160). 2 files in packages/objectql/src/; not governed.
  • Move-only, re-measured by the seat: checkDriversHealth (engine.ts:12664 at merge base 24d350b451) and resolvePrimaryDatasource (:12756) against engine-lifecycle-health.ts (:62, :103): whitespace-insensitive comparison, both identical (784 and 1,465 characters).
  • Net-line rule (ACCEPT 6385306916 on [maintainer] refactor(driver-sql): split src/sql-driver.ts (22,096 lines, +2,533 in 30 days) along its SqlDriver seams, one move-only slice per PR #25521, applied to this series): engine.ts 23,807 → 23,755 (−52), measured by the seat on both trees. Holds.
  • .d.ts (ruling A): raw and normalised sha256 identical at base and head for every published declaration file, as tabled in the PR body.
  • CI on head 7b61487ac1: 34 success, 10 skipped, 0 red; every skip on main's roster. Mergeable clean.
  • Writes: mcp_calls 0; relay pr_create and the report comment.

Landing: pr_ready + automerge_enable through the relay in this act.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 25, 2026 03:32
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 25, 2026 03:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit 97277f1 Oct 25, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25522-objectql-engine-cut-3 branch October 25, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants