Repository navigation
Commit 504d8b3
fix(objectql)!: refuse at boot a controlled_by_parent detail whose master the engine cannot compose a read over (#25166)
Fixes #25144
Clause-②: yes (narrowing)
Metadata-protocol: none
## What
A `controlled_by_parent` detail whose master is in one of ADR-0139's two
uncomposable classes now refuses the **boot**. Before this change the
stack booted, and the id-list pre-scan served it. Once #24932's leaf
switch lands, every read of such a detail would answer `INTERNAL_ERROR`
/ 500. The two classes:
- **(a) D5.3**: a master bound to another datasource than the detail;
- **(b) D5.2**: a master that an object-scoped read middleware narrows
(`registerMiddleware(fn, { object })`), for example `sys_attachment`.
The refusal is `INVALID_METADATA` / 422. It names each detail, its
master field, the master and the class, plus the three remedies: bind
both to one datasource; or choose a master without an object-scoped read
middleware; or declare the detail without `controlled_by_parent`. It is
one error listing every offender. There is no warn-and-continue, no
silent skip and no id-list fallback. The read-time refusal
(`refuseUncomposableReadableIdsClasses`) is unchanged, and its messages
are byte-identical.
- `packages/objectql/src/engine.ts`: the read path's two checks move
into one private predicate, `uncomposableReadableIdsParent(object,
parent)`. It is `hasObjectMiddleware` and then
`resolveDatasourceBinding` on both sides, in the read path's own order.
The read refusal and the new public
`ObjectQL#findUnservableControlledByParentMasters()` both call it, so
the boot rule and the read rule cannot drift. The master is the detail's
one required `master_detail` (`isControlledByParentMasterField`,
ADR-0055 §1).
- `packages/objectql/src/plugin.ts`:
`describeUnservableControlledByParentMasters()`, thrown from
`ObjectQLPlugin`'s `kernel:bootstrapped` handler.
- `.changeset/25144-cbp-unservable-master-boot-refusal.md`: BREAKING,
`patch` (`Metadata-protocol: none`), with the remedy and the ADR-0087
disposition `not-required (no-migration-prescription)`.
## Placement (measured first)
On `origin/main` `23540582f4`, the kernel runs every `start()`, then
`kernel:ready` (handlers in registration order), then
`kernel:bootstrapped`, then `kernel:listening`, which opens the socket.
- The engine plugin registers its `kernel:ready` handler in its own
`start()`, so that handler runs **before** its dependents' handlers.
- Most of the card's object-scoped read middlewares are registered
**inside the owning plugin's `kernel:ready` handler**, not in `start()`:
- service-mailbox: `installMailReadVisibility`;
- plugin-audit: comment, activity, activity redaction, audit-log
redaction, audit-log visibility;
- service-storage: `installAttachmentReadVisibility`.
- plugin-approvals and plugin-security register theirs in `start()`.
- No object-scoped registration runs in any `kernel:bootstrapped` or
`kernel:listening` handler. I grepped every one of them: sharing,
security, auth, automation, mcp, pinyin, storage, organizations.
So `registerApp` sees neither final fact, and the engine's
`kernel:ready` sees the bindings but not the later middlewares.
`kernel:bootstrapped` sees both and is still a boot refusal, because no
socket is open yet. The same precedent exists in
`organizations-plugin.ts#runMembershipPolicyGate`. The placement stays
in `packages/objectql`, so no `needs_decision` is raised.
**Probe (one-off, not committed).** I moved the check into the engine's
`kernel:ready` and removed it from `kernel:bootstrapped`. Class (a)
still went red as expected. The three pins whose middleware is
registered at a plugin's `kernel:ready` passed through unrefused: `3
failed | 5 passed (8)`. The restored blob equals `HEAD`. A first attempt
at this probe was a no-op: `ablation-replace` refused it because the
replacement contained its anchor. It was redone with a different anchor.
## Verification (head `0f5d9a6bc` after merging `origin/main`
`06900c9c2`; the merge brings in no objectql file)
- New pins, `plugin-controlled-by-parent-unservable-master.test.ts`, run
on a real `ObjectKernel` + `ObjectQLPlugin`, with the middleware
registered from a plugin's `kernel:ready` the way the platform's own
gates are:
- class (a);
- class (b);
- class (b) over `sys_attachment`;
- several offenders in one refusal.
- Controls: HotCRM's six `controlled_by_parent` shapes (`e6d5d3e`), with
a gated system object nobody masters on; global middleware (`*` / no
object); both bound to one second datasource; a non-CBP detail across
datasources. 8/8.
- **Ablation**: `if (unservable) throw unservable;` was replaced via
`scripts/ablation-replace.mjs` (anchor 1 → 0). Result: `4 failed | 4
passed (8)`: the four class pins went red and the four controls stayed
green. Restored, blob `2eccc2620e81` equals `HEAD`, and `git diff HEAD`
is empty.
- `@objectstack/objectql` typecheck: exit 0. Full suite: 461/461 files,
8645/8645 tests (on `e0ca492fd`).
- `@objectstack/runtime` full suite: 433/433 files, 6805 passed, 19
skipped (on `e0ca492fd`).
- `pnpm --filter @objectstack/core test:repo`: 6/6 files, 82/82 tests.
- Dogfood battery (`pnpm --filter @objectstack/dogfood test`, full,
under the lock, on `e0ca492fd`): `VERDICT command-exit 0`, Test Files
287 passed + 3 skipped (290), Tests 2395 passed + 18 skipped (2413). It
boots the showcase, whose two `controlled_by_parent` objects are
controls; the CRM example's `crm_opportunity_line_item` is covered by
its suites.
- After the merge: full `turbo run build` (77/77), objectql typecheck
and the new pins, all exit 0 on `0f5d9a6bc`.
- Derived gates: `dispatch-gates.mjs --commands` lists 71 families.
Every one was run with its exit code captured before any pipe, and
`--ran` reconciles 71 derived, 71 run, 0 NOT-MEASURED.
`check:dual-build-cjs-loads` exited 3 (no `dist/`) until the full build,
then exited 0. `check:adr-0087-registration` passes: 1 declared-breaking
changeset, `not-required (no-migration-prescription)`.
## Acceptance notes
- **Late installs.** A manifest registered *after* the boot (a
marketplace install, or `metadata:reloaded`) is not judged by this rule.
Its detail is still refused at read time by name (D5.2 / D5.3,
unchanged). Judging it on arrival would mean resolving bindings for
objects not yet registered. This card asked for the boot.
- **Reference alias.** The boot rule reads the master from the canonical
`reference` key. plugin-security's CBP resolver also tolerates the
rejected `reference_to` / `referenceTo` aliases on raw-registered
objects. A raw-registered alias-spelled detail would boot here and still
be refused at read.
- **Error code.** The refusal uses `INVALID_METADATA` / 422, like the
sibling boot refusals (picklists, bound actions), not D3's
`INTERNAL_ERROR` / 500. That envelope is a composition fault at read
time; this is a declaration refused before anything is served.
- Not touched: `ObjectSchema.parse` / `packages/spec`, `os validate` (it
resolves no deployment bindings or middleware registrations), and
#24932's switch. #25092 and #25106 remain open.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Dw4pQznF7eB7VtCsFQXMAH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b93c750 commit 504d8b3
4 files changed
Lines changed: 343 additions & 8 deletions
File tree
- .changeset
- packages/objectql/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| |||
4845 | 4845 | | |
4846 | 4846 | | |
4847 | 4847 | | |
| 4848 | + | |
| 4849 | + | |
| 4850 | + | |
| 4851 | + | |
| 4852 | + | |
| 4853 | + | |
| 4854 | + | |
| 4855 | + | |
| 4856 | + | |
| 4857 | + | |
| 4858 | + | |
| 4859 | + | |
| 4860 | + | |
| 4861 | + | |
| 4862 | + | |
| 4863 | + | |
| 4864 | + | |
| 4865 | + | |
4848 | 4866 | | |
4849 | 4867 | | |
4850 | 4868 | | |
| |||
6543 | 6561 | | |
6544 | 6562 | | |
6545 | 6563 | | |
6546 | | - | |
6547 | 6564 | | |
6548 | 6565 | | |
6549 | 6566 | | |
6550 | 6567 | | |
6551 | 6568 | | |
6552 | 6569 | | |
6553 | | - | |
| 6570 | + | |
| 6571 | + | |
6554 | 6572 | | |
6555 | 6573 | | |
6556 | 6574 | | |
6557 | | - | |
6558 | | - | |
6559 | | - | |
6560 | | - | |
| 6575 | + | |
| 6576 | + | |
| 6577 | + | |
6561 | 6578 | | |
6562 | 6579 | | |
6563 | 6580 | | |
6564 | 6581 | | |
6565 | 6582 | | |
6566 | 6583 | | |
| 6584 | + | |
| 6585 | + | |
| 6586 | + | |
| 6587 | + | |
| 6588 | + | |
| 6589 | + | |
| 6590 | + | |
| 6591 | + | |
| 6592 | + | |
| 6593 | + | |
| 6594 | + | |
| 6595 | + | |
| 6596 | + | |
| 6597 | + | |
| 6598 | + | |
| 6599 | + | |
| 6600 | + | |
| 6601 | + | |
| 6602 | + | |
| 6603 | + | |
| 6604 | + | |
| 6605 | + | |
| 6606 | + | |
| 6607 | + | |
| 6608 | + | |
| 6609 | + | |
| 6610 | + | |
| 6611 | + | |
| 6612 | + | |
| 6613 | + | |
| 6614 | + | |
| 6615 | + | |
| 6616 | + | |
| 6617 | + | |
| 6618 | + | |
| 6619 | + | |
| 6620 | + | |
| 6621 | + | |
| 6622 | + | |
| 6623 | + | |
| 6624 | + | |
| 6625 | + | |
| 6626 | + | |
| 6627 | + | |
| 6628 | + | |
| 6629 | + | |
| 6630 | + | |
| 6631 | + | |
| 6632 | + | |
| 6633 | + | |
| 6634 | + | |
6567 | 6635 | | |
6568 | 6636 | | |
6569 | 6637 | | |
| |||
| 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 | + | |
0 commit comments