Repository navigation
Commit d8b943b
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XEP6zbU3nytnsHfcYKdNRXA)_
Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160 + 6441032442
## 维护者速读
- **改了什么**:把 `engine.ts` 里写入预览
`validate`(不落库、按写入路径自己的规则给出每行判定)的方法体原样搬进新文件
`engine-validate.ts`,类上的方法签名与注释不变,只把方法体换成转交;方法体与 `insert` / `update` 共用的
6 个工具函数原地加 `export`、由新文件导入(不复制)。顺带把上一刀遗留的两处 `{@link
READABLE_IDS_MAX_CHAIN_DEPTH}` 用一个仅类型导入接回(注释文字一字不改)。`engine.ts` 22,189 →
21,733 行。#26680 与 #26778(都改了 `validate` 方法体)已先合入,各自的三处改动随方法体一起搬进新文件。
- **为什么改**:#25522 按缝拆分 `engine.ts` 的第 16 刀。`validate` 是缝表上收益最大的一块(约
−439),之前被「公开 `.d.ts` 逐字节不变」挡住;新裁决
`6441032442`(B)允许仅声明顺序与分块文件名变化,本刀按该裁决切出。
- **风险与回滚**:纯搬移,无行为变化;公开 `.d.ts`
六个文件在裁决规定的归一化后完全相同(声明集、导出集、声明内容逐字节相同,只是分块里声明的先后与分块名变了);含正控。新文件与
`engine.ts` 互相导入,构建产物里模块顺序只多出新文件一行;一次性探针在新旧两版构建产物上逐行对照(69 行完全一致)。回滚 =
revert 本 PR。
- **先看**:`packages/objectql/src/engine-validate.ts`(搬入的方法体与
`ValidateHost`)、`packages/objectql/src/engine.ts` 里 `validate` 的转交桩与 6
处原地
`export`、`packages/objectql/src/validate-preview-parent.test.ts`(对齐检查改为从新文件读预览调用)。
- **两分钟验证**:`pnpm --filter @objectstack/objectql exec vitest run
--project local --maxWorkers=2
src/validate-preview-parent.test.ts`,应见该文件全部通过(其中 parity 一组从
`engine-validate.ts` 找到唯一的预览调用)。
- **需要你做的**:无,席位落地。
Cut 16 of the `packages/objectql/src/engine.ts` split (#24586 ruling A,
recorded in `6270097840`), under the claim `6441744054`. The body of
`ObjectQL#validate`, the write preview (the write path's own verdict
over candidate rows, persisting nothing), moves unedited into a new
module, `packages/objectql/src/engine-validate.ts`, behind
`ValidateHost`. This is the first cut under the `.d.ts`
declaration-reorder ruling `6441032442` (letter B): the published
declaration chunk is reordered and renamed, and every declaration file
is EQUAL under that ruling's normaliser (report block below, with its
positive control). No behaviour changes. #25522 stays open for the later
cuts.
Branch `claude/project-thread-cjdmc4` (the card thread's harness-pinned
branch), cut from `origin/main` at `fe53769a8e`, which contains cut 15's
merge (`378c940b16`, #26645), with `origin/main` merged in at
`1685a06dd9` (after #26680) and at `ef7067fdaf` (after #26778).
## What changes
### Why this area
Re-measured on `fe53769a8e` with cut 11's measure script and cut 14's
price step (`price3.mjs`), both unchanged. `engine.ts` is 21,947 lines,
6,830 of them from the last 30 days (`git blame
--since=2026-09-27T12:20Z`, clone deepened to 2026-09-21 first); `class
ObjectQL` holds 318 members, 255 methods and accessors. Query-options
erasure sites: 8 at base (lines 8286, 9595, 9841, 9905, 10237, 13517,
13816, 18736), none in `validate`'s range.
<details><summary>Seam table on <code>fe53769a8e</code> (41 sections of
<code>class ObjectQL</code>; U = untyped receivers)</summary>
| range | section | first … last member | lines | 30-day lines | methods
| inferred public | U | private statics |
|---|---|---|---|---|---|---|---|---|
| 4643-4868 | Instance state and constructor | `txStore` … `constructor`
| 226 | 50 | 0 (0 public) | - | 0 | - |
| 4869-4891 | Status and registry accessor | `getStatus` … `registry` |
23 | 0 | 2 (2 public) | getStatus | 0 | - |
| 4892-5235 | Hook and function registry | `registerHook` …
`triggerHooks` | 344 | 6 | 16 (16 public) | registerHook, triggerHooks |
1 | - |
| 5236-5591 | Hook dispatch internals | `hasHooksFor` …
`assertBulkPerRowHookBudget` | 356 | 61 | 7 (0 public) | - | 0 | - |
| 5592-5754 | Action registry and activation | `registerAction` …
`setActionActive` | 163 | 24 | 10 (10 public) | - | 1 | - |
| 5755-6036 | Registered resolvers and write-gate probes |
`_heldFileResolver` … `registerInsertRecomputeListener` | 282 | 116 | 6
(6 public) | - | 0 | - |
| 6037-6661 | Readable-ids security and permission/formula resolution |
`readableIdsSecurityReported` … `resolveDefaultPermissions` | 625 | 492
| 15 (2 public) | - | 0 | - |
| 6662-6768 | Authz invalidation and middleware registration |
`writeEpoch` … `hasObjectMiddleware` | 107 | 18 | 4 (4 public) | - | 0 |
- |
| 6769-6946 | Execution plumbing (middleware, session, provenance,
users) | `executeWithMiddleware` … `buildUser` | 178 | 45 | 7 (0 public)
| - | 0 | - |
| 6947-7155 | Driver options and engine posture | `buildDriverOptions` …
`resolveEnginePosture` | 209 | 150 | 6 (0 public) | - | 0 | - |
| 7156-7365 | Organization probe and system-insert organization |
`probeInstallOrganizations` … `transactionCoversDriverFor` | 210 | 45 |
4 (0 public) | - | 0 | - |
| 7366-7731 | Field defaults | `resolveNowDefault` …
`defaultFieldEntries` | 366 | 4 | 6 (0 public) | - | 0 | - |
| 7732-8344 | Autonumber | `applyAutonumbers` … `seedAutonumber` | 613 |
80 | 6 (0 public) | - | 1 | - |
| 8345-9009 | Package, app, plugin and driver registration |
`registrationConversionWarned` … `registerDriver` | 665 | 170 | 6 (3
public) | registerApp, registerDriver | 0 | - |
| 9010-9218 | Datasource registry | `supplyFileColumnsMovedResolver` …
`listUnavailableDatasources` | 209 | 1 | 8 (7 public) | - | 3 | - |
| 9219-9370 | Write guard and realtime publish | `assertWriteAllowed` …
`publishBulkDataEvent` | 152 | 18 | 4 (1 public) | - | 0 | - |
| 9371-9469 | Validation-message i18n | `setI18nService` …
`objectDisplayLabel` | 99 | 14 | 4 (1 public) | - | 0 | - |
| 9470-10178 | Referential integrity and master-detail resolution |
`assertReferencesResolve` … `masterDeclaredFields` | 709 | 308 | 12 (0
public) | - | 0 | - |
| 10179-10294 | Dangling-reference and cascade inspection |
`inspectDanglingReferences` … `inspectCascadeDelete` | 116 | 48 | 2 (2
public) | - | 0 | - |
| 10295-10389 | Crypto and tenancy providers | `setCryptoProvider` …
`setTenancyPostureProvider` | 95 | 20 | 4 (4 public) | - | 1 | - |
| 10390-10809 | Secret and internal-field handling |
`encryptSecretFields` … `resolveInternalField` | 420 | 61 | 9 (3 public)
| - | 0 | - |
| 10810-10878 | Schema lookup and filter judging | `getSchema` …
`judgeFilter` | 69 | 25 | 3 (2 public) | - | 0 | - |
| 10879-11144 | Datasource routing | `LIFECYCLE_DATASOURCE` …
`setDatasourceMapping` | 266 | 20 | 8 (3 public) | setDatasourceMapping
| 0 | - |
| 11145-11312 | Lifecycle init and health | `init` …
`resolvePrimaryDatasource` | 168 | 16 | 3 (3 public) | init | 0 | - |
| 11313-11386 | Media and value-shape presence | `objectHasMediaField` …
`valueShapeStrictFor` | 74 | 13 | 4 (0 public) | - | 0 |
mediaFieldPresence, coveredValueFieldPresence |
| 11387-11976 | Migration flags and attestations |
`isFileReferencesMigrationVerified` … `wasDatastoreCreatedFromEmpty` |
590 | 96 | 17 (8 public) | - | 0 | - |
| 11977-11999 | destroy | `destroy` … `destroy` | 23 | 0 | 1 (1 public)
| destroy | 0 | - |
| 12000-12018 | Expand/cascade limits (statics) | `MAX_EXPAND_DEPTH` …
`MAX_BULK_PER_ROW_HOOK_ROWS` | 19 | 0 | 0 (0 public) | - | 0 |
MAX_EXPAND_DEPTH, MAX_CASCADE_DEPTH |
| 12019-12469 | Roll-up summaries | `autonumberCounters` …
`recomputeSummaries` | 451 | 0 | 7 (1 public) | - | 0 | - |
| 12470-12723 | Related-record expansion | `expandRelatedRecords` …
`expandRelatedRecords` | 254 | 10 | 1 (0 public) | - | 0 | - |
| 12724-12921 | File-reference hydration | `resolveFileReferences` …
`readParentDerivedFiles` | 198 | 183 | 5 (0 public) | - | 0 | - |
| 12922-13400 | Query lowering | `resolveWhereTokens` …
`requireFindOnePredicate` | 479 | 271 | 9 (0 public) | - | 0 | - |
| 13401-13957 | find / findOne | `find` … `staticReadonlyCreateStrip` |
557 | 142 | 4 (2 public) | - | 0 | - |
| 13958-14615 | validate | `validate` … `validate` | 658 | 447 | 1 (1
public) | - | 0 | - |
| 14616-15962 | insert / insertMany | `readUpdatePriorRow` …
`insertMany` | 1347 | 439 | 3 (2 public) | - | 1 | - |
| 15963-17896 | update | `update` … `update` | 1934 | 275 | 1 (1 public)
| - | 0 | - |
| 17897-19437 | Delete cascade and reference walking |
`planCascadeAtomicity` … `organizationOwnedRowCounts` | 1541 | 733 | 22
(1 public) | - | 0 | storedReferenceIncludes,
remainderAfterMemberRemoval, referenceCheckContext,
outlivesOrganization, keepsOrganizationReference,
organizationCountContext |
| 19438-20007 | delete | `delete` … `delete` | 570 | 47 | 1 (1 public) |
- | 0 | - |
| 20008-20627 | count / aggregate | `count` … `aggregate` | 620 | 192 |
3 (2 public) | - | 0 | - |
| 20628-20936 | execute and transactions | `execute` …
`enforceTransactionOrigin` | 309 | 74 | 8 (2 public) | - | 0 | - |
| 20937-21229 | Compatibility API | `registerObject` … `create` | 293 |
52 | 16 (16 public) | - | 0 | - |
The per-member screen and the full price output are in the project
files, `pm-orders/server/25522-cut16-seam-table.md`.
</details>
| price (net lines) | section | taken? |
|---|---|---|
| -439 priced, **-456 landed** | validate | **yes** (one body: 469 lines
at `fe53769a8e`, 345 of them from the last 30 days; 492 once #26680's
and #26778's lines arrived) |
| -233 | Package, app, plugin and driver registration | no: overlaps
open #26337 (`METADATA_ARRAY_KEYS` and its
`check-stack-collection-maps.mjs` row), as the order says |
| -124 | Delete cascade and reference walking | pays less |
| -71 or less | count / aggregate, Query lowering, the rest | pays less
|
| -27 | `resolveFormulaRelated` (Readable-ids security and
permission/formula resolution) | no: another section, so not one area
with `validate`; a later cut |
### The shape
- The class keeps `validate` with name, modifiers, signature, docblock
and position. Its body becomes one host literal of exactly the
`ValidateHost` members it reads, plus a `.call`, marked `[#25522 cut
16]`, as cuts 8 and 10 to 15 did. Fields go as getters over a `readX`
closure (`_registry`, `_writeWallVerdict`, `logger`, `_writeGateProbe`);
methods go as late-bound closures (`resolveObjectName`,
`permissionResolution`, `resolveDefaultPermissions`,
`initializeSummaryFields`, `applyFieldDefaults`,
`staticReadonlyCreateStrip`, `mediaValueShapeStrictFor`,
`valueShapeStrictFor`, `validationMessageContext`, `buildEvalUser`,
`findOne`, `readUpdatePriorRow`, `resolvePreviewParents`,
`resolvePredicateRelated`, `resolveOptionPermissions`, and the two the
body gained while this cut was in flight: `getSummaryDescriptors` from
#26680 and `assertReferencesResolve` from #26778), so a spy or double
set on the engine is still the one a call reaches. The body assigns no
field, so there is no setter (cut 13's setter ruling has nothing to
apply to). `validate` is public with a declared return type (`Promise`
of `ValidateDataResponse`), so the inferred-return series limit does not
hold it; it has no `this.FIELD.delete/insert/update(` site, no
private-static read and no self-call to `insert` / `update` / `delete`.
- **Helpers, under the cut-14 seat ruling.** The body reads seven
top-level helpers, each also read by bodies that stay:
`undeclaredWriteFieldErrors` and `stripComputedWriteFields` (`insert`,
`update`), `droppedFieldEvents` (`insert`),
`insertStaticReadonlySubject` (`insert`,
`withholdInsertReadonlyFromHooks`), `updateStaticReadonlySubject`
(`update`, `staticReadonlyStoredView`), `shouldSkipStateMachine`
(`insert`, `update`) and `servedAsStored` (already exported for
`engine-master-detail-related.ts`). The six that were not exported gain
`export` in place, byte for byte otherwise (leading docblocks included);
none moves and none is copied. The module imports the seven as values
and `ObjectQL` and `WriteWallRowVerdict` as types from `./engine.js`. It
is a new module, so before that it imported nothing from `./engine.js`.
`index.ts` and `core.ts` name their exports singly, so nothing new is
published. Exporting `insertStaticReadonlySubject` is the export that
reorders the published chunk (below); that is what ruling `6441032442`
now allows.
- **The run-time cycle (measured).** The value import makes `engine.ts`
and the module import each other. The module runs nothing at evaluation
(declarations only), and `engine.ts` imports it right after
`./engine-update-dispatch.js`, which is after every module the body
imports (`validation/record-validator`, `validation/rule-validator`,
`engine-update-dispatch`, the `@objectstack/spec` subpaths), so the
evaluation order of every other module is unchanged. Measured on the
built bundles (`index.mjs`, `core.mjs`, `index.js`, `core.js`): with the
`// src/engine.ts` region markers set aside (9 at base and at head), the
`// src/...` region order at head equals the base's with one line added,
`// src/engine-validate.ts`, between `engine-update-dispatch` and
`integrity/dangling-reference-audit`. The bundles hold both files in one
module, so the published build has no cycle. The late-binding probe
answers the same on base and head (Tests).
- **Imports.** The module imports `RAW_FILE_VALUES_CONTEXT_KEY`,
`EngineQueryOptions` (type), `ExecutionContext` (type),
`ValidateDataIssue` and `ValidateDataResponse` (types),
`WriteObservabilityOptions` (type), `validateRecordInScope`,
`normalizeBlankTypedValues`, `normalizeNumericStringValues`,
`ValidationError`, `valueShapeStrictEffective`, `mediaStrictEffective`,
`evaluateValidationRules`, `hasParentScopedRequiredWhen`,
`stripReadonlyFields`, `stripRuntimeOwnedFields`,
`createBatchSummaryAccumulator` (#26680's, which `engine.ts` keeps for
`insert`) and `resolveEngineUpdateDispatch` with
`EngineUpdateDispatchData` (type). `engine.ts` drops the four whose last
reader moved: `ValidateDataIssue`, `RAW_FILE_VALUES_CONTEXT_KEY`,
`valueShapeStrictEffective` and `mediaStrictEffective` (each from its
existing import line; no line is removed).
- **Fold-in: the two stale `{@link READABLE_IDS_MAX_CHAIN_DEPTH}`.** The
docblocks of `composeReadableIdsAtBoundary` and
`composeReadableIdsChain` stay byte for byte; the link now resolves
through a type-only import, `import type { READABLE_IDS_MAX_CHAIN_DEPTH
} from './engine-readable-ids-permission-formula.js'` (a type-only
import is erased from the JavaScript; TypeScript counts a `{@link}` as a
use), and the constant gains `export` in place in that module (one home,
unchanged value). Measured first the other way, and dropped: re-wording
the two docblocks to name the module changed the published chunk,
because those PRIVATE members' docblocks are printed in the `.d.ts`
(`private composeReadableIdsAtBoundary;` under its docblock). That is a
change inside the `declare class ObjectQL` statement, outside ruling
`6441032442`'s bar, so it was not kept. With the type-only import the
head `.d.ts` is byte-identical to the ruling's measured variant (below).
- The module header names what moved, how the body reaches the class,
and how the helpers were handled.
### Net lines (the seat's rule)
`engine.ts` goes from 22,189 to 21,733 lines, **net -456** (44 added,
500 removed). The module is 607 lines. The diff against `origin/main` is
1,187 changed lines over 5 files (+671 / -516).
### Readers of the moved lines
- **`content/docs/permissions/system-context.mdx` site register.** The
body's one `isSystem` read (the caller-write strips' gate) moves with
it, so the generated register's anchor
`packages/objectql/src/engine.ts#validate` (1 read, row 21b) becomes
`packages/objectql/src/engine-validate.ts#validate`, rebuilt by `node
scripts/check-system-context-census.mjs --write` (after `git add -N` of
the module), never by hand. Row 21b's `Symbol` cell names `validate` by
symbol, so it stays. `scripts/check-system-context-census.mjs`'s
`NON_READ_ANCHORS` has no `validate` row.
- **`packages/objectql/src/validate-preview-parent.test.ts` (f), the
preview/write parity pin.** It read `engine.ts` and attributed each
`evaluateValidationRules` call to its enclosing class method, the one in
`validate` being the preview. The preview's call now lives in the
module's top-level function `validate`, so the reader reads both files
and attributes a call to its enclosing method OR function, and names the
file in its messages. The writes still come from `engine.ts`. It still
finds exactly one preview call (`toHaveLength(1)`), so it cannot pass
empty.
- No other census, ADR anchor, docs row or test key names `validate` by
`engine.ts` path: `objectql/src/engine.ts#NAME` pointers under
`packages/spec/**`, `docs/**`, `content/**` and `scripts/**` name none
of the moved names; `federated-injected-column-readers.test.ts` keys
none; the body calls no `canReadObject(` (core's
`row-serving-door-exposure.pin.test.ts` is unchanged) and no
`referenceTargetOf(` (core's `second-object-read-exposure.pin.test.ts`
is unchanged); it holds no `query-options/no-any-erasure` site (8 at
base and head), no tenant-audit untyped receiver and no private-static
read.
- **No ADR anchor paragraph describes the moved body** (the `engine.ts`
entry is the transaction-seam paragraph), so the module needs no anchor
entry of its own.
- **Read-seam census (the cut-21 trap of #25467).**
`check-durability-degradation-log-level --list` at base and head is
identical once line numbers are masked: 48 durability seams and 88 read
seams (at base `ef7067fdaf`), the same entries; `validate` is named in
none.
- **Decision-letter count:** `check-adr-anchors` at base `ef7067fdaf`
(clean detached worktree): **1,898** decision-letter citations on the
anchored surface, 59,978 resolving citations across 8,400 files. At
head: **1,898**, 59,978 across 8,401 (the +1 file is the new module).
The count held at the earlier bases too (`fe53769a8e` and `1685a06dd9`:
1,871 = 1,871). The moved body cites `ADR-0104` and `ADR-0113` without a
decision letter, so no letter leaves the anchored surface.
## Why
#25522 splits `engine.ts` one seam at a time so that a change to one
area reads, reviews and conflicts as that area alone. `validate` is the
area that pays most (the seam table) and the one ruling `6441032442`
freed; #26680 and #26778, which edited its body while this cut was in
flight, have landed and are carried here (Acceptance notes). Nothing
about what the engine does changes.
## Tests
No test is added. One test's source reader is migrated with the body
(above): the move is proved by identity, not by new behaviour.
<details><summary>Public <code>.d.ts</code>: the declaration-reorder bar
of ruling <code>6441032442</code> item 1, with a positive
control</summary>
Built `@objectstack/objectql` at the base (`ef7067fdaf`, `origin/main`
after #26778: `engine.ts` blob `8116d440cd7e`,
`engine-readable-ids-permission-formula.ts` blob `099ea596128b`, no
`engine-validate.ts`) and at head (`engine.ts` blob `fa37297312da`),
each from a clean tree (`dts-cut16.sh`), after a full `turbo run build`
(116 of 116 tasks). The same bar held at the two earlier bases
(`fe53769a8e` and `1685a06dd9`, the latter with byte-identical
declarations to the former). The builds are the moved code, not a stale
cache: `validate.call(host` occurs 0 times in the base `dist/index.mjs`
and once in the head's.
Normaliser: `pm-orders/server/evidence/25522/dtsnorm.mjs`, sha256
`25226e0677bdad24726e9eff76a7e247bf7319440d2430d28cbaab0db6add94b` (the
ruling's), run as `node dtsnorm.mjs --diff BASE_FILE HEAD_FILE` on the
two `dist/` directories. `6348891160`'s union-order normaliser has
nothing to apply to: the statement-level one closes every diff on its
own.
| file (base → head) | raw sha256 base | raw sha256 head | raw-diff
lines | statements base / head | normalised sha256 (base = head) |
declared-surface digest (base = head) | import / export specifier sets |
|---|---|---|---|---|---|---|---|
| `core.d.ts` | `01b7e638addebd16…` |
`642b5a3662c27aabde2dae4bb5877f515cb89f4c1e8075f21c0598b7e6025206` | 12
| 14 / 14 |
`40e2fde86719d3ccd1cf492314c426c977ab836a5ab54580800cd1f41352247a` |
`4450aaf478092516…` (1 decl) EQUAL | 0 / 84, EQUAL |
| `core.d.mts` | `66eaf00a556923e4…` |
`07f5718a09a2662c2f94bc3d79a8e74b67310efa82071a5d81ed1b716adaa69c` | 12
| 14 / 14 |
`171eab8743fd8dfabc014a162c40c3e034be29f262b26a2bcc24cb3e06be0459` |
`4450aaf478092516…` EQUAL | 0 / 84, EQUAL |
| `index.d.ts` | `f16604a77d499f56…` |
`6dd970eb9c7dc4364e2fe99d22460672edc270302287f812231d823090491122` | 8 |
122 / 122 |
`2bb6a21a929b41c8dbb85c72c600f87ed3a3af12ca731d1cace61f5d4aa3d57e` |
`9136189ce4d0aba3…` (103 decls) EQUAL | 18 / 284, EQUAL |
| `index.d.mts` | `e6a8dff9693367bd…` |
`37153868b48bef0e9ad655d74b549a30f51510977cb704e0d6624db1d8e83781` | 8 |
122 / 122 |
`c7d7bab765f3b0fb35241fa03bd0b716a93139c98e5638d209379dad96528f98` |
`9136189ce4d0aba3…` EQUAL | 18 / 284, EQUAL |
| `util-BcFTQcXa.d.ts` → `util-DdsJlVTF.d.ts` | `d8cd099b508be5ea…` |
`856d98e0e45ced3ec15bf72a1ac09420a2c4f73b5165ab598c1d7d0000705f02` |
1,144 | 185 / 185 |
`a4372dd015163a46e90b82473d806423a5ca982b8bef21e6e8bfed2b0655b9dd` |
`71a464127e0b90b1…` (173 decls) EQUAL | 55 / 147, EQUAL |
| `util-BcFTQcXa.d.mts` → `util-DdsJlVTF.d.mts` | `d8cd099b508be5ea…` |
`856d98e0e45ced3ec15bf72a1ac09420a2c4f73b5165ab598c1d7d0000705f02` |
1,144 | 185 / 185 |
`a4372dd015163a46e90b82473d806423a5ca982b8bef21e6e8bfed2b0655b9dd` |
`71a464127e0b90b1…` EQUAL | 55 / 147, EQUAL |
- (a) Every file: `statements only in base: 0; only in head: 0`,
declared-surface digest EQUAL.
- (b) Every file's normalised sha256 is EQUAL base to head.
- (c) The import and export specifier sets of every declaration file are
EQUAL; the run-time export lists are too (`index` 182 names and `core`
54, ESM and CJS, the same sorted list at base and head).
- (d) The raw diff, by its shape: in the four entries, the chunk name in
the first `import`/`export` line and the side-effect `import
'@objectstack/spec/…'` lines in another order (12 and 8 lines); in the
chunk, the `rule-validator` declaration block printed earlier (hunks
`3598a3599,4164` and `11504,12069d12069`), the external `import`
statements in another order and two specifier lists in another order.
The chunk is renamed `util-BcFTQcXa` → `util-DdsJlVTF`. Nothing else
differs; the normalised files are identical.
- **The same reorder as the ruling measured.** The chunk diff has the
shape of the `export function insertStaticReadonlySubject` alone variant
measured for `6441032442`
(`pm-orders/server/evidence/25522/dts-reorder-ruling-measure.md`): the
same `rule-validator` block printed earlier, the same line counts. At
the earlier base `1685a06dd9` the head was that variant byte for byte;
main has since added declarations of its own, so the raw hashes are new.
- (e) After the head build, the dependents' closure build
(`@objectstack/rest`, `@objectstack/runtime`, `@objectstack/client`,
`@objectstack/client-react` and `@objectstack/core` with their
dependencies, whose DTS builds type-check against it) and `pnpm --filter
@objectstack/core typecheck` ran on the head `dist/`: exit 0 each.
- Premises re-checked at this head: (P1) above; (P2)
`packages/objectql/package.json` `exports` names `./dist/index.*` and
`./dist/core.*` only, `driver-sql`'s `./dist/index.*` only, so no chunk
is a published path; (P3) `git grep -n -E
'objectql/dist|driver-sql/dist' -- scripts package.json`: 0 hits.
**Positive control.** One `sed` dropped `private` from ` private async
readUpdatePriorRow(` on the head (a member the moved body reads through
the host; anchor count 1 to 0, replacement count 0 to 1; `engine.ts`
blob `fa37297312da` to `843d3085ea88`) and rebuilt (`posctl16.sh`, build
exit 0): the chunk was renamed `util-BKP1J1qR`, which now publishes
`readUpdatePriorRow(object: string, id: unknown, context:
ExecutionContext | undefined, base?: unknown): Promise…` (line 10556);
its normalised sha256 is
`d84c4ef94b1e5c90f96d9e23107fe38c3378bdcf7adb9f341f6357f8108435a8` ≠
`a4372dd0…`, and its declared-surface digest `e4dc581b5cbaa0f8…` ≠
`71a464127e0b90b1…` (the `declare class ObjectQL` statement named as the
one changed). `index.d.ts` normalises EQUAL under the control (an entry
only re-exports), which is why every declaration file is read. The
restore leg put the blob back to `fa37297312da` (`git diff HEAD` empty),
and the head was rebuilt before every later step.
</details>
<details><summary>AST compare (move-only)</summary>
`astcmp.mjs` (TypeScript compiler API, comments and whitespace ignored,
tokens compared) on base `engine.ts` against head `engine.ts` plus the
module:
- The moved body is **SAME** against base `ef7067fdaf` (parameters,
return type, `async`, body tokens, and every body line equal after one
level of indent): `validate` 2,238 tokens / 492 lines, #26680's and
#26778's hunks included. (Against the earlier bases it was SAME too:
`fe53769a8e` 2,093 / 469, `1685a06dd9` 2,148 / 476.)
- `class ObjectQL`: 324 of 324 members kept, in the same order; 323
byte-identical (both re-pointed docblocks included), and the stub keeps
docblock, modifiers and signature identical.
- Top level: 222 of 232 base statements byte-identical. The other 10
(`exportcmp.mjs`): the 3 import lines that lose a name; the 6 helpers
that gained `export` and are otherwise byte-identical with their leading
docblocks; and the class. The head adds two statements: the module
import and the type-only import.
- `engine-readable-ids-permission-formula.ts`: one statement changes,
`const READABLE_IDS_MAX_CHAIN_DEPTH = 8;` gains `export` (its docblock
and value unchanged).
</details>
<details><summary>Late-binding probe (base and head
<code>dist</code>)</summary>
`zz-probe-cut16.mjs` (a one-off, not committed; in the project files)
loads the built `ObjectQL` and drives `validate` with doubles set on the
instance after construction for the members whose real answer needs a
datasource (`mediaValueShapeStrictFor`, `valueShapeStrictFor`,
`findOne`, `readUpdatePriorRow`), the other eleven host methods wrapped
to record the call order, `getSummaryDescriptors` and
`assertReferencesResolve` replaced in their own cases, and `_registry` /
`logger` replaced on the instance; then it prints results, the call
order and the log lines. Cases: an insert that passes; a required field
missing (two rows); a bad option; an undeclared key (thrown
`INVALID_FIELD`); the computed and static-`readonly` strips with the
`onFieldsDropped` events; the same under `isSystem`; a listener that
throws (logged and ignored); `update` by id (the read door, then the
stored row); `update` whose row the read door does not serve; `update`
with no id; strict and warn-first value-shape postures;
`_writeWallVerdict` set after construction (one row refused, one
judged); a wall answering the wrong count (thrown); `_writeGateProbe`
set after construction denying, and throwing; `findOne` replaced after
construction; `logger` replaced after construction; an unknown object;
`getSummaryDescriptors` replaced after construction, reached by a
two-row insert and not asked by a one-row insert;
`assertReferencesResolve` replaced after construction, reached on each
insert row (its throw on the second row surfaces as it does at base) and
not run by an update preview. The 69 output lines are identical on base
`ef7067fdaf` and head (the engine's construction log lines, which carry
timestamps, set aside): the getters read what the instance holds when
the body reads it, and the closures reach the replaced methods.
</details>
<details><summary>Suites</summary>
Under `scripts/pm/os-verify-lock.sh` on head `3d228114aa`:
- Closure build of `@objectstack/objectql`'s dependencies, then of
`@objectstack/rest`, `@objectstack/runtime`, `@objectstack/client`,
`@objectstack/client-react` and `@objectstack/core` with their
dependencies: exit 0.
- `typecheck` of `@objectstack/objectql` and `@objectstack/core`: exit 0
each.
- Full `turbo run build` (the root `build` filters): 116 of 116 tasks,
exit 0.
- `@objectstack/objectql` vitest `--project local`: 475 files, 8,843
tests passed, exit 0 (the migrated parity pin, with #26778's
insert-preview case, and #26680's `summary-batch-accumulation.test.ts`
among them).
- The two door tests that drive the multi-row `validate` preview, `pnpm
--filter @objectstack/rest exec vitest run
src/import-batch-parent-summary-rule.test.ts
src/import-dry-run-batch-parent-summary.test.ts` (#26680's and
#26778's): 2 files, 18 of 18 passed, exit 0.
- `pnpm --filter @objectstack/core exec vitest run
src/utils/import-runner` (#26778's dry-run callers of the preview): 16
files, 186 tests passed, exit 0.
- Every `packages/*/vitest.repo-tests.json` project: core 6 files / 83
tests, objectql 1 / 5, rest 5 / 280 passed and 18 skipped, runtime 3 /
947, spec 80 / 1,295, types 1 / 11. Exit 0 each.
</details>
## Verification
Head `3d228114aa43f1a727ac431f005090b996b74437`. The gate list is `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this diff at that head (85 commands,
merge base `ef7067fdaf`), each run with its exit code captured before
any pipe, then reconciled with `--ran`.
- **Derived gates: 85 of 85 run, exit 0 each; `--ran`: 85 run, 0
NOT-MEASURED.**** `check:dual-build-cjs-loads` ran after a full `turbo
run build` (116 of 116 tasks); `check-plugin-teardown-shape --self-test`
after fetching `621a4876` and deepening the clone
(`--shallow-since=2026-07-22`). The exit codes are in
`pm-orders/server/evidence/25522/cut16-ran-final.txt`.
- **Hand gates, exit 0 each:** `node scripts/check-adr-anchors.mjs`
(counts above), `node scripts/check-tenant-audit-census.mjs`, `node
scripts/check-adr-symbol-anchors.mjs`, `node
scripts/check-platform-checklist.mjs --changed-from origin/main`, `node
scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`, `node
scripts/check-durability-degradation-log-level.mjs --list`, `node
scripts/check-system-context-census.mjs`, and eslint as `pnpm lint` runs
it (`--no-inline-config`) on `packages/objectql`.
## Acceptance notes
- **Serial order.** Nothing was edited before the claim `6441744054`
named this branch. The branch is cut from `fe53769a8e`, which holds cut
15's merge (`378c940b16`). `origin/main` was merged three times: at
`f6ffd72347` (no conflict), at `1685a06dd9` once #26680 landed, and at
`ef7067fdaf` once #26778 landed. The second and third merges conflicted
in `validate`'s body, as expected. Each resolution keeps the stub in
`engine.ts`, adds the member the body newly reads to its host literal
(`getSummaryDescriptors`, then `assertReferencesResolve`), and applies
the PR's three body hunks to the same lines of `engine-validate.ts`, one
indent level in. #26778's docblock hunk applies to `engine.ts`, where
the docblock stays.
- **Overlap.** Open PRs touching `packages/objectql/**` or a re-keyed
reader, listed at start and again just before opening this. #26680
(Fixes #25579) edited `validate`'s body while this cut was in flight;
under the seat's ruling (option A on the open question in the report on
#25522) this branch waited for it, merged `main` after it landed, and
carries its three preview hunks in `engine-validate.ts` (the AST compare
above is against `1685a06dd9`, after #26680). #26778 (Fixes #26684)
edited it next (a sequential `judgeRow` loop and the insert's reference
check after the rules); under seat ruling `6446486417` this PR opened at
`07d67daf3e` without landing ahead of it, then merged `main` once #26778
landed and carries its hunks, with `assertReferencesResolve` as one more
late-bound `ValidateHost` member (the AST compare above is against
`ef7067fdaf`, after both). #26755 and #26337 edited `engine.ts` outside
`validate` and have both landed; they merged without conflict. Re-read
at this head: the only other open PR touching `packages/objectql/**` is
#26769 (a test file only), and `git merge-tree` of this head with
`origin/main` `0ea90c7a18` exits 0. Whichever lands second merges
`main`.
- **The fold-in, measured both ways.** The order asked for the two stale
`{@link READABLE_IDS_MAX_CHAIN_DEPTH}` to be re-pointed comment-only.
Re-wording them is not `.d.ts`-neutral: the docblocks of private members
are printed in the published chunk, so the first build of this cut (with
the two docblocks re-worded to name the module) left one residual
statement, `declare class ObjectQL`, after the normaliser, which is
outside `6441032442`'s bar. The type-only import keeps both docblocks
byte for byte and makes the link resolve; it costs one `export` on the
constant in its own module. Recorded here so a later cut does not
re-word a private member's docblock expecting no `.d.ts` change.
- **`resolveFormulaRelated` stays** (about -27): it belongs to another
section, so taking it here would make this two areas. Its blocker
(`FormulaRelated` reorders the chunk) is gone under the same ruling, so
it can lead a later cut. **Package, app, plugin and driver
registration** (about -233) follows once #26337 lands.
- **Receivers.** The host literal carries `_registry`,
`_writeWallVerdict`, `logger` and `_writeGateProbe` as getters, so the
body reads whatever the instance holds when it reads it: the probe
replaces `_writeWallVerdict`, `_writeGateProbe`, `logger` and `findOne`
on the instance after construction and sees the moved body use the
replacements. No body assigns a field, so there is no setter.
- **Left as is.** The interface docblock text on `ValidateHost` is the
series' shared paragraph, word for word as on
`ReadableIdsPermissionFormulaHost`. The orphaned `insert` docblock and
comment block that sit above `validate`'s own docblock in `engine.ts`
stay where they are, byte for byte (moving them would be an edit, not a
move).
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(Not touched.)
- [x] 2. Every name I added uses the casing for its kind
(`ValidateHost`, `engine-validate.ts`).
- [x] 3. No formatter ran; `pnpm lint` passes on the touched package.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. No publish-visible change, so `skip-changeset` (ruling
`6441032442` item 4); both declaration lines are filled in above.
- [x] 6. `packages/spec` not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`; the
`system-context.mdx` register is regenerated by its script.
- [x] 8. Title, body and comments are in English, apart from the
maintainer quick-read block the project asks for.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
`skip-changeset`: move-only inside `@objectstack/objectql`, with the
published declarations EQUAL under ruling `6441032442` item 1 and no
behaviour change, so there is nothing for a release note to say.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5oafXXU7gvgcHxdZb9
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3c2187f commit d8b943b
5 files changed
Lines changed: 671 additions & 516 deletions
File tree
- content/docs/permissions
- packages/objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
529 | 529 | | |
530 | 530 | | |
531 | 531 | | |
| 532 | + | |
532 | 533 | | |
533 | 534 | | |
534 | 535 | | |
535 | 536 | | |
536 | 537 | | |
537 | 538 | | |
538 | | - | |
539 | 539 | | |
540 | 540 | | |
541 | 541 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
112 | | - | |
| 112 | + | |
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| |||
0 commit comments