Repository navigation
Commit 378c940
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XW9mBRdGopgsLvLN2tj9V1s)_
Part of #25522
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
## 维护者速读
- **改了什么**:把 `engine.ts` 里「ReadableIds 安全与权限/公式解析」一段的 8 个方法体(ReadableIds
读取范围链的组合、启动时 controlled_by_parent
主表的检查、单个父对象读取范围的询问、一次操作共享的权限解析及其三个使用方、公式故障的一次性告警)原样搬进新文件
`engine-readable-ids-permission-formula.ts`,类上的方法签名与注释不变,只把方法体换成转交;只被这些方法体用到的常量
`READABLE_IDS_MAX_CHAIN_DEPTH` 随之搬走,与留下的代码共用的 2 个工具函数和 4 个类型原地加
`export`、由新文件导入(不复制)。`engine.ts` 22,092 → 21,947 行。
- **为什么改**:#25522 按缝拆分 `engine.ts` 的第 15 刀(18.2.0
发布后重启)。缝表重测后,`validate`(约 −439)仍受 `.d.ts` 重排的裁决所限;第二名「包、应用、插件与驱动注册」(约
−233)与在途的 #26337 重叠(同一个 `METADATA_ARRAY_KEYS` 及其门禁),所以取不重叠的第三名。
- **风险与回滚**:纯搬移,无行为变化;公开 `.d.ts` 六个文件逐字节不变(含正控);新文件与 `engine.ts`
互相导入(运行期循环),已用一次性探针在新旧两版构建产物上逐行对照(78 行完全一致)。回滚 = revert 本 PR。
-
**先看**:`packages/objectql/src/engine-readable-ids-permission-formula.ts`(搬入的方法体与
`ReadableIdsPermissionFormulaHost`)、`packages/objectql/src/engine.ts` 里
`composeReadableIdsChain` 的转交桩(泛型、递归经实例方法回入)与原地
`export`、`packages/core/src/security/row-serving-door-exposure.pin.test.ts`(一行改键)。
- **两分钟验证**:`pnpm --filter @objectstack/objectql exec vitest run
--project local --maxWorkers=2`,应见全部测试文件通过。
- **需要你做的**:无,席位落地。
Cut 15 of the `packages/objectql/src/engine.ts` split (#24586 ruling A,
recorded in `6270097840`), the first after the release pause
(`6434935278`; restart record `6439182543`, `@objectstack/spec@18.2.0`).
The claim is `6439254992`. Eight bodies of the "Readable-ids security
and permission/formula resolution" section move unedited into a new
module,
`packages/objectql/src/engine-readable-ids-permission-formula.ts`:
`composeReadableIdsChain` (the chain of composed `ReadableIds` read
scopes), `findUnservableControlledByParentMasters` (the boot screen over
`controlled_by_parent` details), `readableIdsScopeOf` (the scope one
leaf is composed with), `resolveOptionPermissions`,
`permissionResolution`, `resolveFormulaPermissions` and
`resolveDefaultPermissions` (an operation's one effective-permission
resolution and its three consumers), and `reportFormulaFault`. No
behaviour changes. #25522 stays open for the later cuts.
Branch `claude/project-thread-firh6t` (the card thread's harness-pinned
branch), cut from `origin/main` at `f9867020c`, which contains cut 14's
merge (`00874b9e09`, #26489) and the 18.2.0 release.
## What changes
### Why this area
Re-measured on `f9867020c` with cut 11's measure script and cut 14's
price step (`price3.mjs`), both unchanged: a helper a paying body shares
with a staying body is exported in place and blocks nothing; one read
only by moving bodies moves with them. Both earlier screens still apply
(query-options erasure sites, 8 at base, none in this section;
module-helper closure). `engine.ts` is 22,092 lines, 7,009 of them from
the last 30 days (`git blame --since=2026-09-27T10:15Z`, clone deepened
to 2026-09-22 first). `scripts/pm/git-history.mjs count`: 130 commits in
30 days, 35 in 7. `class ObjectQL` holds 318 members, 255 methods and
accessors. Since cut 14, one commit touched the file (#26551, +7 lines
in "Action registry and activation"); the release added no new section.
<details><summary>Seam table on <code>f9867020c</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 |
|---|---|---|---|---|---|---|---|---|
| 4659-4884 | Instance state and constructor | `txStore` … `constructor`
| 226 | 50 | 0 (0 public) | - | 0 | - |
| 4885-4907 | Status and registry accessor | `getStatus` … `registry` |
23 | 0 | 2 (2 public) | getStatus | 0 | - |
| 4908-5251 | Hook and function registry | `registerHook` …
`triggerHooks` | 344 | 6 | 16 (16 public) | registerHook, triggerHooks |
1 | - |
| 5252-5607 | Hook dispatch internals | `hasHooksFor` …
`assertBulkPerRowHookBudget` | 356 | 61 | 7 (0 public) | - | 0 | - |
| 5608-5770 | Action registry and activation | `registerAction` …
`setActionActive` | 163 | 24 | 10 (10 public) | - | 1 | - |
| 5771-6052 | Registered resolvers and write-gate probes |
`_heldFileResolver` … `registerInsertRecomputeListener` | 282 | 116 | 6
(6 public) | - | 0 | - |
| 6053-6806 | Readable-ids security and permission/formula resolution |
`readableIdsSecurityReported` … `resolveDefaultPermissions` | 754 | 546
| 15 (2 public) | - | 0 | - |
| 6807-6913 | Authz invalidation and middleware registration |
`writeEpoch` … `hasObjectMiddleware` | 107 | 18 | 4 (4 public) | - | 0 |
- |
| 6914-7091 | Execution plumbing (middleware, session, provenance,
users) | `executeWithMiddleware` … `buildUser` | 178 | 45 | 7 (0 public)
| - | 0 | - |
| 7092-7300 | Driver options and engine posture | `buildDriverOptions` …
`resolveEnginePosture` | 209 | 150 | 6 (0 public) | - | 0 | - |
| 7301-7510 | Organization probe and system-insert organization |
`probeInstallOrganizations` … `transactionCoversDriverFor` | 210 | 45 |
4 (0 public) | - | 0 | - |
| 7511-7876 | Field defaults | `resolveNowDefault` …
`defaultFieldEntries` | 366 | 4 | 6 (0 public) | - | 0 | - |
| 7877-8489 | Autonumber | `applyAutonumbers` … `seedAutonumber` | 613 |
80 | 6 (0 public) | - | 1 | - |
| 8490-9154 | Package, app, plugin and driver registration |
`registrationConversionWarned` … `registerDriver` | 665 | 170 | 6 (3
public) | registerApp, registerDriver | 0 | - |
| 9155-9363 | Datasource registry | `supplyFileColumnsMovedResolver` …
`listUnavailableDatasources` | 209 | 1 | 8 (7 public) | - | 3 | - |
| 9364-9515 | Write guard and realtime publish | `assertWriteAllowed` …
`publishBulkDataEvent` | 152 | 18 | 4 (1 public) | - | 0 | - |
| 9516-9614 | Validation-message i18n | `setI18nService` …
`objectDisplayLabel` | 99 | 14 | 4 (1 public) | - | 0 | - |
| 9615-10323 | Referential integrity and master-detail resolution |
`assertReferencesResolve` … `masterDeclaredFields` | 709 | 308 | 12 (0
public) | - | 0 | - |
| 10324-10439 | Dangling-reference and cascade inspection |
`inspectDanglingReferences` … `inspectCascadeDelete` | 116 | 48 | 2 (2
public) | - | 0 | - |
| 10440-10534 | Crypto and tenancy providers | `setCryptoProvider` …
`setTenancyPostureProvider` | 95 | 20 | 4 (4 public) | - | 1 | - |
| 10535-10954 | Secret and internal-field handling |
`encryptSecretFields` … `resolveInternalField` | 420 | 61 | 9 (3 public)
| - | 0 | - |
| 10955-11023 | Schema lookup and filter judging | `getSchema` …
`judgeFilter` | 69 | 48 | 3 (2 public) | - | 0 | - |
| 11024-11289 | Datasource routing | `LIFECYCLE_DATASOURCE` …
`setDatasourceMapping` | 266 | 20 | 8 (3 public) | setDatasourceMapping
| 0 | - |
| 11290-11457 | Lifecycle init and health | `init` …
`resolvePrimaryDatasource` | 168 | 16 | 3 (3 public) | init | 0 | - |
| 11458-11531 | Media and value-shape presence | `objectHasMediaField` …
`valueShapeStrictFor` | 74 | 13 | 4 (0 public) | - | 0 |
mediaFieldPresence, coveredValueFieldPresence |
| 11532-12121 | Migration flags and attestations |
`isFileReferencesMigrationVerified` … `wasDatastoreCreatedFromEmpty` |
590 | 96 | 17 (8 public) | - | 0 | - |
| 12122-12144 | destroy | `destroy` … `destroy` | 23 | 0 | 1 (1 public)
| destroy | 0 | - |
| 12145-12163 | 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 |
| 12164-12614 | Roll-up summaries | `autonumberCounters` …
`recomputeSummaries` | 451 | 0 | 7 (1 public) | - | 0 | - |
| 12615-12868 | Related-record expansion | `expandRelatedRecords` …
`expandRelatedRecords` | 254 | 10 | 1 (0 public) | - | 0 | - |
| 12869-13066 | File-reference hydration | `resolveFileReferences` …
`readParentDerivedFiles` | 198 | 183 | 5 (0 public) | - | 0 | - |
| 13067-13545 | Query lowering | `resolveWhereTokens` …
`requireFindOnePredicate` | 479 | 272 | 9 (0 public) | - | 0 | - |
| 13546-14102 | find / findOne | `find` … `staticReadonlyCreateStrip` |
557 | 142 | 4 (2 public) | - | 0 | - |
| 14103-14760 | validate | `validate` … `validate` | 658 | 447 | 1 (1
public) | - | 0 | - |
| 14761-16107 | insert / insertMany | `readUpdatePriorRow` …
`insertMany` | 1347 | 439 | 3 (2 public) | - | 1 | - |
| 16108-18041 | update | `update` … `update` | 1934 | 275 | 1 (1 public)
| - | 0 | - |
| 18042-19582 | Delete cascade and reference walking |
`planCascadeAtomicity` … `organizationOwnedRowCounts` | 1541 | 733 | 22
(1 public) | - | 0 | storedReferenceIncludes,
remainderAfterMemberRemoval, referenceCheckContext,
outlivesOrganization, keepsOrganizationReference,
organizationCountContext |
| 19583-20152 | delete | `delete` … `delete` | 570 | 47 | 1 (1 public) |
- | 0 | - |
| 20153-20772 | count / aggregate | `count` … `aggregate` | 620 | 192 |
3 (2 public) | - | 0 | - |
| 20773-21081 | execute and transactions | `execute` …
`enforceTransactionOrigin` | 309 | 74 | 8 (2 public) | - | 0 | - |
| 21082-21374 | Compatibility API | `registerObject` … `create` | 293 |
52 | 16 (16 public) | - | 0 | - |
The per-member screen (movable bodies, what stays and why) and the full
price output are in the project files,
`pm-orders/server/25522-cut15-seam-table.md`.
</details>
| price (net lines) | section | taken? |
|---|---|---|
| -439 | validate | no: the `.d.ts` reorder ruling (exporting
`insertStaticReadonlySubject`), unchanged since cut 14 |
| -233 | Package, app, plugin and driver registration | no: overlaps
open #26337, which edits `METADATA_ARRAY_KEYS` (read only by
`registerMetadataCollections`, so it would move) and its gate row in
`scripts/check-stack-collection-maps.mjs` |
| -156 priced, **-145 landed** | Readable-ids security and
permission/formula resolution | **yes**, without `resolveFormulaRelated`
(below) |
| -124 | Delete cascade and reference walking | pays less |
| -71 or less | count / aggregate, Query lowering, the rest | pays less
|
**Why `resolveFormulaRelated` stays.** Its return type `FormulaRelated`
is a non-exported type that top-level helpers that stay also use
(`formulaHopScope`, `applyFormulaPlan`, `hydrateWriteFormulas` and
`StoredExpressionServing`), so moving the body means exporting it in
place. Measured by exporting each shared declaration on its own on the
base and building (`dts-export-variant-cut15.sh`):
`writeFailureLogMeta`, `isExpressionDefault`, `PermissionResolution`,
`FormulaPlanEntry`, `ReadableIdsSecurity` and `ReadableIdsChain` each
leave all six declaration files byte-identical; `FormulaRelated`, which
names `RelatedRecordBinding` from `validation/rule-validator`, makes the
declaration bundler pull the validator declarations in earlier: the same
declarations in another order, with the chunk renamed (`util-DOIDsRyc`
to `util-BWosSWLp`). That is the same mechanism that keeps `validate`
out, and the claim's byte-identical bar forbids it, so
`resolveFormulaRelated` stays (39 body lines, about -30 of the priced
-156). A first full move that included it is how this was found: its
build reordered the chunk, and the variant measurement named the one
export responsible.
### The shape
- The class keeps all eight methods with name, modifiers, signature,
docblock and position. Seven bodies become one host literal of exactly
the `ReadableIdsPermissionFormulaHost` members they read, plus a
`.call`, marked `[#25522 cut 15]`, as cuts 8 and 10 to 14 did. Fields go
as getters over a `readX` closure (`_registry`, `logger`,
`_effectiveObjectPermissionsResolver`, `formulaFaultReported`); methods
go as late-bound closures (`refuseUncomposableReadableIdsClasses`,
`readableIdsScopeOf`, `composeReadableIdsChain`,
`uncomposableReadableIdsParent`, `buildEvalUser`,
`defaultFieldEntries`), so a spy or double set on the engine is still
the one a call reaches. No moved body assigns a field, so there is no
setter (cut 13's setter ruling has nothing to apply to).
- `composeReadableIdsChain` is generic and recursive: its stub calls the
function instantiated with its type parameter `N` and `.call`s it on the
host, the spelling #25467's generic cuts used, and the host's
`composeReadableIdsChain` closure re-enters through the method, so the
recursion still passes the instance (the probe counts both levels).
- `resolveOptionPermissions` reads no member of the class, so its method
calls the function directly (`return resolveOptionPermissions(schema,
payloads, resolution);`).
- **Helpers and types, under the cut-14 seat ruling.**
`READABLE_IDS_MAX_CHAIN_DEPTH` is read only by
`composeReadableIdsChain`, so it moved with it, docblock included and
byte for byte. `writeFailureLogMeta` (read too by `reportFindFailure`,
`insert`, `update`, `delete`) and `isExpressionDefault`
(`applyFieldDefaults`) stay where they were, byte for byte, and gain
`export` in place; so do the types the moved bodies share with staying
code: `PermissionResolution`, `FormulaPlanEntry`, `ReadableIdsSecurity`
and `ReadableIdsChain` (each `export` alone was measured
`.d.ts`-neutral, above). `UnservableControlledByParentMaster` was
already exported. The module imports the two functions as values and the
five types 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 (below).
- **The run-time cycle (measured).** That 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-master-detail-related.js`, after every module the moved bodies
import, 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, the
`// src/...` region order at head equals the base's with one line added,
the new module, between `engine-master-detail-related` and
`in-memory-aggregation`. One `engine.ts` region disappears: the one that
held only the external `@objectstack/spec/data` import of `ReadableIds`,
`RLS_DENY_FILTER`, `markFilterSubtreeProvenance` and
`isControlledByParentMasterField`, whose readers all moved; that import
now sits in the module's region (an external package already loaded by
earlier regions; ESM imports are hoisted). 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 `ReadableIds`, `RLS_DENY_FILTER`,
`isControlledByParentMasterField`, `markFilterSubtreeProvenance`,
`FilterCondition` (type), `ExecutionContext` (type),
`toEvalPermissions`, `EvalError` and `EvalPermissions` (types),
`collectComposableReadableIds`, `composeReadableIdsLeaves`,
`describeReadableIdsLeaf`, `readableIdsRefusalError`,
`ComposableReadableIdsSite` (type), `optionVisibilityReadsPermissions`
and `readsPermissionPredicate`. `engine.ts` drops
`composeReadableIdsLeaves`, the whole `ReadableIds` / `RLS_DENY_FILTER`
/ `markFilterSubtreeProvenance` / `isControlledByParentMasterField`
import line, and `toEvalPermissions`, whose last readers moved. It keeps
`optionVisibilityReadsPermissions` and `readsPermissionPredicate`: no
code in `engine.ts` reads them any more, but the docblocks that stay on
`resolveOptionPermissions` and `resolveFormulaPermissions` `{@link}`
them, the same use `DatasourceUnavailableError` already has there at
base (TypeScript counts a `{@link}` as a use under `noUnusedLocals`),
and that import line stays byte-identical.
- The module header names what moved, what stays and why, and how the
helpers were handled.
### Net lines (the seat's rule)
`engine.ts` goes from 22,092 to 21,947 lines, **net -145** (59 added,
204 removed). The module is 362 lines. With the one path reader below,
the diff against `origin/main` is 628 changed lines over 3 files (+423 /
-205).
### Readers of the moved lines
- `packages/core/src/security/row-serving-door-exposure.pin.test.ts`
lists every non-test file that calls a security service's
`canReadObject(`. `engine.ts`'s only call was in `readableIdsScopeOf`,
so its row moves to the new module, with its `not-a-door` classification
and reason unchanged (the reason already described exactly this ask:
"the ReadableIds composition (ADR-0139 D2) …"). Without the re-key the
census would read `engine.ts` as stale and the module as unclassified.
- No other census, ADR anchor, docs row or test key names a moved member
or helper by `engine.ts` path: `objectql/src/engine.ts#NAME` pointers
under `packages/spec/**`, `docs/**`, `content/**` and `scripts/**` name
none of them; `content/docs/permissions/system-context.mdx` and its
`NON_READ_ANCHORS` have no row for them (no moved body reads
`isSystem`); `federated-injected-column-readers.test.ts` keys none of
them (its `resolveFormulaRelated` row stays with that body in
`engine.ts`); no moved body calls `referenceTargetOf(` (core's
`second-object-read-exposure.pin.test.ts` is unchanged); no moved body
holds a `query-options/no-any-erasure` site (8 at base and head), a
tenant-audit untyped receiver or a private static read. Mentions by bare
name (plugin-security's `READABLE_IDS_MAX_CHAIN_DEPTH` comments,
`plugin.ts`'s `ObjectQL#findUnservableControlledByParentMasters`) name
the constant and the method, which still exist under those names.
- **No ADR anchor paragraph describes a 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 82 read
seams, the same entries. No moved body is named in a read-seam entry.
- **Decision-letter count:** `check-adr-anchors` at base `f9867020c`
(clean detached worktree): 91 anchored files, **1,881** decision-letter
citations on the anchored surface, 59,819 resolving citations across
8,353 files. At head: 91 anchored files, **1,880**, 59,820 across 8,354.
The one letter that leaves the anchored surface is `[ADR-0139 D6, as
amended — the nested master chain]` in `READABLE_IDS_MAX_CHAIN_DEPTH`'s
docblock, which moved with the constant into the module, which no anchor
entry names; the citation still resolves (it is in the 59,820), it is
just no longer counted on the anchored surface. The +1 resolving
citation and +1 file are the new module (its header names ADR-0139
once). The same one-letter move was disclosed on cuts 8 and 12 of
#25467; anchoring the module to keep the count would mean writing a new
invariant paragraph, which is not a move.
## 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. This cut takes the
section that pays most under this cut's rulings and that no open PR
touches (table above). Nothing about what the engine does changes.
## Tests
No test is added or changed apart from the re-keyed row above: the move
is proved by identity, not by new behaviour.
<details><summary>Public <code>.d.ts</code> byte-identical, with a
positive control</summary>
Built `@objectstack/objectql` at the base (`f9867020c`, `engine.ts` blob
`9c44e9da88b5`) and at head (`engine.ts` blob `19a84bece0b0`), each from
a clean tree (`dts-cut15.sh`), and hashed every declaration file. All
six are byte-identical:
| file | sha256 (base = head) |
|---|---|
| `core.d.mts` |
`e2913d082b71f3342f9013f7e75afad720488366f922beef28bbeee67a8cc81b` |
| `core.d.ts` |
`79919dabaf5a0bf7a81be2c09eee1535b789699f8e6363064e40fa8007f26c14` |
| `index.d.mts` |
`e6368a2e3c25d19f03cfbc471fbdd763daf9ca19de8cf5ce22f4493c8eca5f28` |
| `index.d.ts` |
`74cb25f41cf0aa8d94e495967ed228dcaaeeba8ba8fa8c54fab10eb01ddc1b51` |
| `util-DOIDsRyc.d.mts` |
`791b203b5e36d557cd0d59f15c317b9d85ef644e5ed730b5f3a6aa801fd37adf` |
| `util-DOIDsRyc.d.ts` |
`791b203b5e36d557cd0d59f15c317b9d85ef644e5ed730b5f3a6aa801fd37adf` |
The builds are the moved code, not a stale cache:
`composeReadableIdsChain.call(host` occurs 0 times in the base
`dist/index.mjs` and once in the head's.
**Positive control.** `scripts/ablation-replace.mjs` dropped `private`
from ` private async readableIdsScopeOf(` on the head (anchor count 1 to
0; `engine.ts` blob `19a84bece0b0` to `c8dcf0a369f7`) and rebuilt: every
declaration hash changed (`core.d.mts` `7f089f7719d3…`, `core.d.ts`
`ef7645cf34d2…`, `index.d.mts` `6927c24b7c73…`, `index.d.ts`
`3d457f36059b…`) and the chunk was renamed `util-BFa4erHR.d.ts`, which
now publishes `readableIdsScopeOf(object: string, operation: string,
parent: string, svc: ReadableIdsSecurity, …)` (line 5646). So the
comparison sees a one-word change to a moved member's signature. The
restore leg put the blob back to `19a84bece0b0` (`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:
- Moved bodies, all eight **SAME** (parameters, return type, type
parameters, `async`, body tokens, and every body line equal after one
level of indent): `composeReadableIdsChain` 380 tokens / 46 lines,
`findUnservableControlledByParentMasters` 201 / 12, `readableIdsScopeOf`
253 / 29, `resolveOptionPermissions` 149 / 19, `permissionResolution` 80
/ 11, `resolveFormulaPermissions` 178 / 25, `reportFormulaFault` 101 /
14, `resolveDefaultPermissions` 223 / 21.
- `class ObjectQL`: 318 of 318 members kept, in the same order; 310
byte-identical, and the 8 stubs keep docblock, modifiers and signature
identical.
- Top level: 217 of 228 base statements byte-identical. The other 11
(`exportcmp.mjs`): the 3 import edits above; 6 declarations that gained
`export` and are otherwise byte-identical with their leading docblocks
(`writeFailureLogMeta`, `isExpressionDefault`, `PermissionResolution`,
`FormulaPlanEntry`, `ReadableIdsSecurity`, `ReadableIdsChain`);
`READABLE_IDS_MAX_CHAIN_DEPTH`, byte-identical in the module (1,137
characters with its docblock); and the class. The head adds one
statement, the module import.
</details>
<details><summary>Late-binding probe (base and head
<code>dist</code>)</summary>
`zz-probe-cut15.mjs` (a one-off, not committed) loads the built
`ObjectQL` and drives all eight methods with doubles set on the instance
for every member the hosts read (`_registry`, `logger`,
`_effectiveObjectPermissionsResolver`, `formulaFaultReported`,
`refuseUncomposableReadableIdsClasses`, `readableIdsScopeOf`,
`composeReadableIdsChain`, `uncomposableReadableIdsParent`,
`buildEvalUser`, `defaultFieldEntries`, plus `hasObjectMiddleware` and
`resolveDatasourceBinding` under them), then prints results, the call
order on the doubles and the log lines. Cases: the `ReadableIds` chain
through `composeReadableIdsAtBoundary` (a nested two-level chain, which
re-enters `composeReadableIdsChain` through the instance; a cycle,
refused; a denied parent; no restriction; `getReadFilter` throwing; the
admission throwing; the `explain` fallback; the deny sentinel; a
non-filter answer; a `readableIdsScopeOf` double replaced after
construction); `findUnservableControlledByParentMasters` over both D5
classes; `permissionResolution` with no user, with a user (asked once
for two `get()`s) and with no resolver; `resolveOptionPermissions`,
`resolveFormulaPermissions` and `resolveDefaultPermissions` with no
resolution, a resolution and a throwing one; `reportFormulaFault` twice
on one pair (one line), and with a `formulaFaultReported` double set
after construction. The 78 output lines are identical on base and head
(the engine's own construction log lines, which carry timestamps, set
aside), which is the check the seat's ruling asks for on the run-time
cycle. The module-order measurement on the bundles is under "The
run-time cycle" above.
</details>
<details><summary>Suites</summary>
Under `scripts/pm/os-verify-lock.sh` on head `4d72947a5` (after merging
`main`):
- Closure build of `@objectstack/objectql`, `@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.
- `@objectstack/objectql` vitest `--project local`: 472 files, 8,795
tests passed, exit 0.
- Every `packages/*/vitest.repo-tests.json` project: core 6 files / 83
tests (it holds the re-keyed census), objectql 1 / 5, rest 5 / 280
passed and 18 skipped, runtime 3 / 947, spec 80 / 1,293, types 1 / 11.
Exit 0 each.
- The `.d.ts` pair and the late-binding probe were run on both bases:
`f9867020c` before the merge and `ce9bc104e` after it, with the same six
hashes and the same 78 probe lines both times (`engine.ts` is the same
blob at both bases).
</details>
## Verification
Head `4d72947a56925b533ac8ad37659bcf47c615ea75`. The gate list is `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this diff at that head (56 commands,
merge base `ce9bc104e`, no STALE warning), each run with its exit code
captured before any pipe, then reconciled with `--ran`.
- **Derived gates: 56 of 56 exit 0.** `--ran` reconciliation: `56
derived famil(ies) accounted for — 56 run, 0 NOT-MEASURED`.
`check:dual-build-cjs-loads` refused on its first pass (exit 3, a
package's `dist/` not yet built) and was re-run once the build closure
was complete: 112 entry points across 69 packages load, exit 0.
`check-engine-split-ratio` and `check-plugin-teardown-shape` ran after
`git fetch --shallow-since=2026-07-22 origin main` and `git fetch origin
621a487`.
- **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`, and eslint
as `pnpm lint` runs it (`--no-inline-config`) on `packages/objectql` and
`packages/core`.
## Acceptance notes
- **Serial order and restart.** Nothing was edited before the claim
`6439254992` named this branch. The branch is cut from `f9867020c`,
which holds cut 14's merge (`00874b9e09`) and the 18.2.0 release; `main`
(`ce9bc104e`) was merged in once before opening (clean; no merged commit
touches `engine.ts`, which is the same blob at both bases).
- **Overlap.** Open PRs touching `packages/objectql/**` or the re-keyed
core pin, listed at start and again just before opening this: #26337
(`engine.ts` and other objectql files), #26599
(`validation/rule-validator.test.ts` only) and #26517 (the release PR:
`CHANGELOG.md`, `package.json`). #26337 edits `engine.ts` in the
imports, `OperationContext`, `METADATA_ARRAY_KEYS`, after
`SECURITY_SERVICE_SOURCE_SLOT`, and in `readUpdatePriorRow` / `insert` /
`update`; none of those is moved here, and it does not touch the core
pin. `git merge-tree` of this head with #26337's current head
(`cbd27d9a6c`) exits 0, no conflict. Whichever lands second merges
`main`.
- **`resolveFormulaRelated` stays, measured.** Moving it needs
`FormulaRelated` exported, and that export alone reorders the published
declaration chunk (Why this area). It is the same question the restart
carries for `validate` (may a move-only cut reorder declarations inside
a published `.d.ts` chunk when the declared surface is unchanged?), so a
ruling on that question would free both: `validate` (-439) and
`resolveFormulaRelated` (about -30).
- **Package, app, plugin and driver registration is next once #26337
lands** (about -233). It was not taken here only because #26337 edits
`METADATA_ARRAY_KEYS` and its `check-stack-collection-maps.mjs` row;
that row's `file:` and the constant would move with
`registerMetadataCollections`.
- **Receivers.** The host literals carry `_registry`, `logger`,
`_effectiveObjectPermissionsResolver` and `formulaFaultReported` as
getters, so each body reads whatever the instance holds when it reads
it: the probe replaces `formulaFaultReported` and `readableIdsScopeOf`
on the instance after construction and sees the moved bodies use the
replacements. No body assigns a field, so there is no setter.
- **Left as is.** Two docblocks that stay in `engine.ts`
(`composeReadableIdsAtBoundary`'s and `composeReadableIdsChain`'s) still
say `{@link READABLE_IDS_MAX_CHAIN_DEPTH}`, which now lives in the
module, and `optionVisibilityReadsPermissions` /
`readsPermissionPredicate` stay imported in `engine.ts` for the
`{@link}`s on `resolveOptionPermissions` and `resolveFormulaPermissions`
(above). Editing a staying docblock would break its byte identity, so
these are noted here rather than touched, as cut 14 noted
`staticReadonlyStripTakes`.
- **Decision-letter count 1,881 → 1,880**, explained under "Readers of
the moved lines": one `[ADR-0139 D6 …]` citation moved with its constant
off the anchored surface; nothing was rewritten or dropped.
## 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
(`ReadableIdsPermissionFormulaHost`,
`engine-readable-ids-permission-formula.ts`).
- [x] 3. No formatter ran; `pnpm lint` passes on the touched packages.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. No publish-visible change, so `skip-changeset`; 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/`.
- [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 byte-identical 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_01FEkQ5nM7fbhbE84nNy6VkE
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 86ae47e commit 378c940
3 files changed
Lines changed: 423 additions & 205 deletions
File tree
- packages
- core/src/security
- objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
104 | | - | |
| 104 | + | |
| 105 | + | |
105 | 106 | | |
106 | 107 | | |
107 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 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 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
0 commit comments