Skip to content

refactor(objectql): facade ObjectRepository and ScopedContext in engine.ts with the codemod's second pass (body-identical; 20/24 methods) - #30264

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-qq3rpa
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-qq3rpa

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #30052

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:scripts/codemod-split-engine.mjs 加了第二遍 --scoped,用同一套切法和证明把 engine.ts 里的 ObjectRepository、ScopedContext 门面化:24 个方法里 20 个方法体原样移进 engine-object-repository.ts / engine-scoped-context.ts,每个实例一个 host(engine-scoped-hosts.ts)。生成物未手改。
  • 为什么改:维护者在 refactor(objectql): generate the engine.ts section split with a deterministic codemod #30012 上说「我觉得文件还是太长,还有进一步的拆分优化空间吗」「可以后续立卡处理」「合并之后再继续」;refactor(objectql): generate the engine.ts section split with a deterministic codemod #30012 只门面化了 ObjectQL。
  • 风险与回滚:只是搬移。20/20 方法体逐字节相同,撤销拆分逐字节还原 base engine.ts;公开 .d.ts 相等;objectql 测试 base 548 文件 / 10,863 = head;回滚就是 revert。
  • 先看:① 下方「留在类上的 4 个方法」表;② engine-scoped-hosts.ts 和两个类末尾的 static 块;③ 门禁 check-facade-delegation-only 改为一个文件可登记多个 facade 类。
  • 两分钟验证:pnpm install --frozen-lockfile 后运行 node scripts/codemod-split-engine.mjs --scoped --verify --base 5fa9375e,应看到 20/20 moved bodies identical、13587 -> 13522 lines,退出码 0;--self-test 86 例通过。
  • 需要你做的:改动共 1,553 行(未超 3,000 行线),本 PR 保持草稿,按席位安排落地。

What changes

  • scripts/codemod-split-engine.mjs: a second pass, --scoped. It facades the two classes engine.ts declares after ObjectQL, with the first pass's cut pattern, stay rules and byte-identity proof, and touches no other byte.
    • Input and proof. The pass reads the engine.ts the first pass left (any main after Codemod split: generate the objectql engine.ts split with a deterministic script, verified byte-identical #29986) and is proved against that same file, so it is re-run on the latest main at landing, never rebased. Its marker is [#30052 codemod], so the two passes' stubs and files are told apart. --scoped --verify --base <ref> re-proves the split on disk.
    • One facet module per class (SCOPED_CLASSES): engine-object-repository.ts (ObjectRepositoryFacet) and engine-scoped-context.ts (ScopedContextFacet), each a stateless class of static methods that are the bodies, unedited, with this typed as a Pick of the class's host interface (members typed AS the class members).
    • One host per instance (engine-scoped-hosts.ts, InstanceHost<E, H>): refactor: compress the codemod facade stubs in engine.ts and protocol.ts — one shared host per facet instead of a per-call host literal (body-identical; about 1,000 lines off engine.ts) #30051's shape, keyed by the instance instead of the engine. Each class's static block installs its factory; a method goes as a closure called on the instance at call time (spies and overrides are still reached), a field as a getter, and a field a body assigns would also get a setter (no moved body assigns one). The module imports types only, so it forms no evaluation cycle.
    • Two new stay reasons. foreign-private: the body reads a private or protected instance member off another instance (legal in the class body, refused in a module function). private-static: the body reads a non-public static (the statics route is ObjectQL's; the scoped classes declare none).
    • One fix to the already-moved rule. A method was taken for a hand-series stub when an engine-* import shares its name. It now also has to read that import. ObjectRepository#count and #execute share names with the hand-series count and execute and read neither, so they move.
    • The first pass is unchanged. Run on its base c2d353d2 with this script, it prints the same output tree sha256 as refactor(objectql): one shared host per facet in the engine.ts codemod stubs (body-identical; engine.ts 14,140 -> 13,548) #30083 (09c80319…e3554).
    • --self-test: 86 cases in 7 batteries (was 68 in 6). The new battery "scoped classes (second pass)" (floor 16) covers the stay rules, the proof, determinism, the hosts and the refusals, and feeds the proof inputs it must refuse.
  • The generated split. --scoped --write on main 5fa9375e, unedited.

What moved

Class Moved (20) Stays (4)
ObjectRepository all 11: find, findOne, insert, create, update, updateById, delete, deleteById, count, aggregate, execute none
ScopedContext object, sudo, withRunAs, releaseJoinedHandle, txDriver, beginTransaction, commitTransaction, rollbackTransaction, serveFormulaField see below

Public docblocks stay on the class (ruling 6624345766 item 1). The two private methods' docblocks (releaseJoinedHandle, txDriver) moved with their bodies.

Bodies that stay on the class (4, as #30019 exception rows)

Member Reason Card
ScopedContext.transaction (137 lines) bare this: callback(this, …) hands the instance on #30020
ScopedContext.[SEED_USER_GRANTS] (72) bare this (return this), and a symbol-keyed name #30020
ScopedContext.[APP_AUTHORED_HOOK_API] (19) a symbol-keyed name; it also writes appAuthored on another instance #30147
ScopedContext.derive (6) foreign-private: it writes child.appAuthored #30147

Line counts

File Before (5fa9375e) After
packages/objectql/src/engine.ts 13,587 13,522 (−65)
engine-object-repository.ts — 152
engine-scoped-context.ts — 174
engine-scoped-hosts.ts — 51

The two classes were 696 lines. Most of what stays is docblocks on public members (they stay by ruling) and the four bodies above, so engine.ts loses 65 lines: 155 body lines out, 80 stub lines in, plus the imports and the two install blocks.

The #30019 gate (check-facade-delegation-only)

  • The registry was keyed by file, so a second class in engine.ts was refused as "registered twice". A facades row is now keyed by file and class. An exceptions row may name its class, and must when its file registers more than one facade. The success line now counts classes and files.
  • Registry: two facades rows (ObjectRepository, ScopedContext, card refactor: facade ObjectRepository and ScopedContext in engine.ts with the same codemod (about 700 lines), as the ObjectQL class already is #30052), the four exception rows above, and "class": "ObjectQL" added to the 30 existing engine.ts rows (their content is unchanged).
  • --self-test: 37 cases (floor 36 → 37). New case E10: two facades in one file are each judged; a row without its class there, a class that is not registered, and one class registered twice each go red.
  • Gate on this head: ObjectRepository 13/13 members are delegations; ScopedContext 17 delegations, 2 fields, 4 named exceptions; 5 facade class(es) in 3 file(s) hold delegations only, 92 named exception(s).

Readers of engine.ts by path

Each one was run on this head and passed. None needed a change, because no moved body carries a site any of them counts:

  • objectql federated-injected-column-readers.test.ts (in the suite below).
  • core second-object-read-exposure.pin.test.ts and row-serving-door-exposure.pin.test.ts (7/7).
  • check-system-context-census: OK, register unchanged.
  • eslint query-options/no-any-erasure (pnpm lint): exit 0.

Verification

All at head 8b84cef8 unless stated.

  • Byte-identity. node scripts/codemod-split-engine.mjs --scoped --verify --base 5fa9375e: exit 0. 20/20 moved bodies identical (body, parameters, return type, async). Every stub equals the generated one-call stub, the host module and both install blocks are the generated ones, and the undo rebuilds base engine.ts byte for byte (0 import bindings pruned). engine.ts 13587 -> 13522 lines.
  • Determinism. Two --scoped --write runs from base engine.ts (sha256 83dfea29…2d774), one in a separate worktree, print the same output tree sha256 3bbd66188e82e6a18a5b9f871a67b052f237e49e2ba2bd6f9d4f4d286d335a66. Both trees equal the committed head byte for byte.
  • .d.ts. packages/objectql/dist built at base 5fa9375e twice and at head twice. Base 1 = base 2 and head 1 = head 2, byte for byte. Base against head: core and index (.d.ts and .d.mts) are byte-identical once the shared chunk's content hash in the import specifier is normalized. util-HASH has 193/193 statements and 332/332 private members; its public surface (each class's private members removed with their docblocks) is EQUAL. The raw difference is the two moved private docblocks, 21 lines. None of the facet, host or InstanceHost names appears in any .d.ts. Controls: a private member made public is caught, and a public return type changed is caught.
  • Suites. objectql test: base 548 files / 10,863 passed, head 548 / 10,863. test:repo: 1 file / 5 = 5. objectql typecheck (tsc + check:test-typecheck): exit 0.
  • Build and lint. npx turbo run build --concurrency=3 exit 0. pnpm lint exit 0.
  • Gates. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 75 commands and each was run. All exit 0. One (check-plugin-teardown-shape --self-test) exited 3 on the first sweep because its pinned fixture commit was missing in the shallow clone; it passed after git fetch origin 621a4876…. --ran: 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN.
  • Run by hand. codemod-split-engine.mjs --self-test (86 cases) and check-facade-delegation-only.mjs --self-test (37 cases).

Acceptance notes

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request. (the designated thread branch, as the Claim records)
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6Cbo4rDxRL3yugqM5c7

@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tooling labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a.

⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 anchor(s) matched too much of the corpus to be a work list: objectName (symbol, 56 pages), objectName (literal, 56 pages)
  • 14 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 21 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 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 98bad9035bc8664662eea96a2d1e64deefa54c2d — the merge of head 8b84cef82a1f421776040e70dddc6ec36db58e85 into base 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a, 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 98bad9035bc8664662eea96a2d1e64deefa54c2d && git checkout 98bad9035bc8664662eea96a2d1e64deefa54c2d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a 8b84cef82a1f421776040e70dddc6ec36db58e85 && git checkout -B drift-repro 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a && git merge --no-ff 8b84cef82a1f421776040e70dddc6ec36db58e85

node scripts/docs-audit/affected-docs.mjs --json 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a

⚠️ 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 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a → 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

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

席位核对

ACCEPT · head 8b84cef82a1f421776040e70dddc6ec36db58e85 · card #30052 (claim 6648704582) · 2026-11-05T04:34Z

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 04:35
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 04:35
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 343ddb2 Nov 5, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-qq3rpa branch November 5, 2026 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: facade ObjectRepository and ScopedContext in engine.ts with the same codemod (about 700 lines), as the ObjectQL class already is

1 participant