Repository navigation
Commit 13aa6a3
refactor(objectql): move the ObjectQL constructor body into the hooks-and-functions facet (E3) (#31151)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiP9RS1KjicEPyef7VNhFzFa)_
domain:server#2 · marchtian · session_017ihL2fL8RrJDkowuFhXUPG
Refs #30020
Clause-②: no
Metadata-protocol: none
Skills-docs: none — an internal move inside `@objectstack/objectql` plus
a build script; nothing a metadata author writes changes
## 维护者速读
- 改了什么:给 `scripts/codemod-split-engine.mjs` 加了构造函数能力和新规则 R8(确定赋值
wiring),再用它把 `ObjectQL` 构造函数体搬进 `engine-hooks-and-functions.ts` 的
`construct`;构造函数只留签名、一行 `this.logger = …constructLogger(hostContext)`
和一次 `construct.call(…)`。登记表删掉构造函数这一行例外(17 → 16)。这是 #30020 §5 系列的 E3。
- 为什么改:构造函数是登记表里一条 "state-owning" 例外。`logger` 是严格模式下只能在构造函数里赋值的字段,整句挪出就是
TS2564,所以席位裁定走方案 A:R8 只把这一句的赋值留在类上、右值挪进 facet,其余语句按 R1 照搬。
- 风险与回滚:纯搬移,同样的语句、同样的顺序执行。撤销证明逐字节通过;三个公开 `.d.ts` 与 main
构建结果逐字节相同;objectql 全量测试通过。回滚即 revert 本 PR。
- 先看:`packages/objectql/src/engine.ts` 的构造函数(3
行);`engine-hooks-and-functions.ts` 的 `constructLogger` /
`construct`;codemod 里
`definiteAssignmentsOf`、`ctorStubBody`、`verifyCtorMove`、`ctorUndoText`。
- 两分钟验证:`node scripts/codemod-split-engine.mjs --members --verify --base
origin/main` 预期 exit 0,输出 “1 moved method(s) (constructor); engine.ts
10739 -> 10712 lines; undoing the moves does rebuild the base …
byte-for-byte”。
- 需要你做的:无,席位落地。codemod 新增了一条规则,是否要契约复审由席位决定。
## What changes
- **Codemod capability: the constructor (`--members constructor`).** The
members pass now plans, writes and proves a move of `ObjectQL`'s
constructor. The body becomes `static construct(this: Pick<Host, …>, …):
void` in its section's facet. It takes only the parameters it reads,
because the package builds with `noUnusedParameters`. The constructor
keeps its signature and becomes the marker line, the R8 assignments, and
one `Facet.construct.call(host.of(this), …)`.
- **New rule R8, definite-assignment wiring (seat ruling: option A).** A
field a strict compiler requires the constructor to assign is one with a
declared type, no initializer, no `?`/`!`, and a type that does not
admit `undefined`. That assignment stays on the class. Each **leading**
`this.F = EXPR;` whose EXPR reads no `this`, `super` or `arguments` and
no top-level name of `engine.ts` becomes `this.F =
Facet.constructF(<params EXPR reads>)`. `constructF` returns EXPR, typed
as `ObjectQL['F']`. The facet body keeps a marker line where the
statement stood, and the undo turns the marker back into the statement.
Any other write to such a field keeps the constructor whole (stay reason
`definite-assignment`). A class that already declares `construct`
refuses (`ctor-facet-name`).
- **The move (E3).** `node scripts/codemod-split-engine.mjs --members
constructor --write` on `main` `af3451b08b`. Output tree sha256
`f0552217…`, the same on a second run from the base. It writes:
- in `engine.ts`, the 3-line constructor and three new host-factory
entries (`_registry`, `manifests`, `registerMiddleware`);
- in `engine-hooks-and-functions.ts`, the two facet members, three
`HooksAndFunctionsHost` members and four imports;
- four import bindings pruned from `engine.ts`
(`STORED_METADATA_TYPE_READ_GATE_OBJECTS`, `createLogger`,
`startSummaryRefreshOnEnable`, `storedMetadataTypeReadGate`).
Nothing was hand-edited. `engine.ts` goes from 10,739 to 10,712 lines.
- **`scripts/facade-delegation-registry.json`**: the `ObjectQL` ·
`constructor` row (`#30020`) is removed. Exceptions 17 → 16. The
`#30020` rows left in `engine.ts` are `update`, `insert`, `delete`,
`[SEED_USER_GRANTS]` and `transaction`. The gate accepts the new
constructor as wiring: `this.x = <forwarding call>` plus a forwarding
call.
- **`.changeset/30020-objectql-constructor-facet.md`**:
`@objectstack/objectql` patch (the source maps ship).
- **Anchors:** none to repoint. No `packages/spec/liveness/*.json` or
`docs/qa/platform-checklist/**` anchor cites any of the four pruned
names as `engine.ts#…`, or the constructor. `packages/spec/**` is
untouched.
### Constructor statements and how each moved
| # | Statement | Rule | Where it is now |
|---|---|---|---|
| 1 | `this.logger = hostContext.logger \|\| createLogger({…});` |
**R8** (`logger` is `private logger: Logger;`, a required field) | The
assignment stays on the class: `this.logger =
HooksAndFunctionsFacet.constructLogger(hostContext);`. The right side is
in `constructLogger`. |
| 2 | `this._registry.onPackageUninstalled((id) => {
this.manifests.delete(id); });` | host read (`_registry`, `manifests`
getters) | `construct`, byte for byte |
| 3 | `this._registry.onPackageEnabled((id) =>
startSummaryRefreshOnEnable(this, this.logger, id));` | **R1** (`this` →
`this.facade`) | `construct` |
| 4 | `if (OBJECTQL_STRICT_HOOKS) (this as any)._strictHookBinding =
true;` | **R1** (`(this.facade as any)…`) | `construct` |
| 5 | `if (OBJECTQL_WARN_LEGACY_HANDLER) (this as
any)._warnLegacyHandler = true;` | **R1** | `construct` |
| 6 | `for (…) this.registerMiddleware(storedMetadataTypeReadGate, {
object });` | host method (`registerMiddleware`) | `construct` |
| 7 | `this.logger.info('ObjectQL Engine Instance Created');` | host
read (`logger` getter) | `construct` |
Measured before the move:
- No written field is `readonly`.
- No field initializer reads a field the constructor writes, and the
class has no `super`.
- The constructor runs after every field initializer, as before;
statement order is unchanged. `logger` is assigned first, then
`construct` runs the rest, so `this.logger` reads the same value it read
before.
- Statements 4–5 write through the engine itself (`this.facade`), not a
host setter, so the flags land on the engine as before.
### Rules applied
- **Ruling A (6687865760)**: the facade stays the sole state owner. No
field moves.
- **R1** (identity port): three sites, statements 3–5.
- **R8** (new, this PR): statement 1.
- **R3, R4, H2, R7**: not reached.
- **Stop condition**: not hit. Statement 1 is the case the order names
(a field only a constructor may assign). The seat ruled option A, a
codemod capability, instead of a partial move or a terminal row; B (a
`!` on the field) was rejected as weakening the compiler's check.
## Tests
- **Codemod self-test**: a new battery, "constructor and
definite-assignment wiring (fourth pass, #30020 R8)", 17 cases, floor
pinned at 17.
- Positive: the constructor moves and proves; two runs are
byte-identical; exact stub, R8 member and `construct` shapes; imports
pruned and moved; setters only for fields the body writes.
- Refusals: a required field written after another statement; an R8
right side reading `this`; one reading a top-level name of `engine.ts`;
a class declaring `construct`.
- Tampered heads that must fail: an edited R8 right side; a stub missing
its R8 line; a marker naming another member; a statement moved above the
marker.
- No package test changes: a byte-proven move with no behaviour change.
## Verification
Readings were taken at `b03d09ffb5` (base `af3451b08b`). `3a2c4b4027`
merges `origin/main` `9f61e06e6f`, two commits that touch none of these
files. At the merge, the undo proof (`--verify --base origin/main`, exit
0, same line) and `--ran` (75/75) were re-run.
<details><summary>Undo proof (byte-exact), determinism, R8
ablation</summary>
- `node scripts/codemod-split-engine.mjs --members --verify --base
origin/main`: exit 0. "undo proof: 1 moved method(s) (constructor);
engine.ts 10739 -> 10712 lines; undoing the moves does rebuild the base
engine.ts and its modules byte-for-byte once the 4 import binding(s)
only moved bodies read are pruned from the base
(STORED_METADATA_TYPE_READ_GATE_OBJECTS, createLogger,
startSummaryRefreshOnEnable, storedMetadataTypeReadGate)".
- Determinism: regenerating from the base files with the final codemod
writes `engine.ts` (`b612db91…`) and `engine-hooks-and-functions.ts`
(`e6599e4b…`) byte-identical to the committed ones (`cmp` clean). Input
sha256 `9f090702…` = `git show
af3451b:packages/objectql/src/engine.ts | sha256sum`.
- **R8 ablation** (temporary local patch, files restored and
hash-checked afterwards):
- R8 off: `definiteAssignmentsOf` returns nothing. The codemod first
refuses on its own ("host member logger is read-only there, and a moved
body assigns it"). Forcing past that refusal and the undo gate, the
generated constructor is the bare `construct.call(…, hostContext)`, and
`tsc --noEmit` in `packages/objectql` fails: `src/engine.ts(529,11):
error TS2564: Property 'logger' has no initializer and is not definitely
assigned in the constructor.` (exit 2).
- R8 on: the committed tree, `tsc --noEmit` exit 0.
- `node scripts/codemod-split-engine.mjs --self-test`: exit 0, 186 cases
in 12 batteries (169 in 11 before).
</details>
<details><summary>Public declaration files</summary>
`@objectstack/objectql` was built with `pnpm --filter
@objectstack/objectql build`, with the two source files at base
`af3451b08b` and then at head. `dist/core.d.ts` (`a8fd9093…`),
`dist/index.d.ts` (`3a798237…`) and `dist/util-BK7LVy6k.d.ts`
(`24ed9187…`) are byte-equal: `diff -r` is empty. Control: the stub text
`HooksAndFunctionsFacet.construct.call(hooksAndFunctionsHost.of(this))`
occurs 0 times in the base `dist/index.mjs` and once in the head's.
</details>
<details><summary>Package suites and gates</summary>
- `pnpm --filter @objectstack/objectql typecheck` (tsc plus
`check:test-typecheck`) and `pnpm --filter @objectstack/objectql test`,
under `os-verify-lock`: VERDICT command-exit 0. Test Files 564 passed
(564), Tests 11422 passed (11422).
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 75 families, all 75 exit 0, recorded as
`CMD :: exit N`. `--ran`: 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN,
at `b03d09ffb5` and again at `3a2c4b4027`. The facade gate is among them
(`pnpm check:facade-delegation-only`, 16 named exceptions).
- Outside the derived set:
- `node scripts/check-facade-delegation-only.mjs --self-test`: exit 0,
37 cases.
- `pnpm lint`: exit 0.
- `packages/spec` `scripts/liveness/evidence.test.ts`: 48/48, since four
names leave `engine.ts`.
- `pnpm check:platform-checklist`: exit 0, 1145/1155 symbol anchors
resolved, 10 on the named #16898 residual.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6a01718 commit 13aa6a3
5 files changed
Lines changed: 492 additions & 66 deletions
File tree
- .changeset
- packages/objectql/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
| 44 | + | |
41 | 45 | | |
42 | 46 | | |
43 | 47 | | |
| |||
61 | 65 | | |
62 | 66 | | |
63 | 67 | | |
| 68 | + | |
64 | 69 | | |
65 | 70 | | |
66 | 71 | | |
| |||
70 | 75 | | |
71 | 76 | | |
72 | 77 | | |
| 78 | + | |
| 79 | + | |
73 | 80 | | |
74 | 81 | | |
75 | 82 | | |
| |||
87 | 94 | | |
88 | 95 | | |
89 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
90 | 137 | | |
91 | 138 | | |
92 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
128 | 127 | | |
129 | 128 | | |
130 | 129 | | |
| |||
138 | 137 | | |
139 | 138 | | |
140 | 139 | | |
141 | | - | |
142 | 140 | | |
143 | 141 | | |
144 | 142 | | |
| |||
152 | 150 | | |
153 | 151 | | |
154 | 152 | | |
155 | | - | |
| 153 | + | |
156 | 154 | | |
157 | 155 | | |
158 | 156 | | |
| |||
707 | 705 | | |
708 | 706 | | |
709 | 707 | | |
710 | | - | |
711 | | - | |
712 | | - | |
713 | | - | |
714 | | - | |
715 | | - | |
716 | | - | |
717 | | - | |
718 | | - | |
719 | | - | |
720 | | - | |
721 | | - | |
722 | | - | |
723 | | - | |
724 | | - | |
725 | | - | |
726 | | - | |
727 | | - | |
728 | | - | |
729 | | - | |
730 | | - | |
731 | | - | |
732 | | - | |
733 | | - | |
734 | | - | |
735 | | - | |
736 | | - | |
737 | | - | |
738 | | - | |
739 | | - | |
740 | | - | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
741 | 711 | | |
742 | 712 | | |
743 | 713 | | |
| |||
9877 | 9847 | | |
9878 | 9848 | | |
9879 | 9849 | | |
| 9850 | + | |
9880 | 9851 | | |
9881 | 9852 | | |
9882 | 9853 | | |
| |||
9885 | 9856 | | |
9886 | 9857 | | |
9887 | 9858 | | |
| 9859 | + | |
| 9860 | + | |
9888 | 9861 | | |
9889 | 9862 | | |
9890 | 9863 | | |
| |||
0 commit comments