Repository navigation
Commit eb9ef79
Fixes #21516
Clause-②: yes (narrowing)
An in-process engine verb now refuses an object name the schema registry
does
not resolve with the data door's own `OBJECT_NOT_FOUND` (404), instead
of
handing that name to the driver as a raw table name. One name space for
the
in-process verbs and the generic data door (triage ruling). The engine's
accept
set narrows; no surface is widened. The `yes` half of the clause line is
the one
new export, `objectNotFoundError`, in `@objectstack/core`.
## What changed
- **`packages/core` (new `objectNotFoundError`).** One factory for the
`OBJECT_NOT_FOUND` / 404 envelope, beside `recordNotFoundError` and for
the
same ADR-0076 D2 reason (the engine closure cannot import the package
where
the door's envelope was written). Both doors now build the refusal here.
- **`packages/metadata-protocol` (the door).** `assertObjectRegistered`
raises
that shared factory: same wire status, same code.
- **`packages/objectql` (the engine).** `resolveObjectName` throws
`objectNotFoundError` for a name the registry does not resolve, rather
than
returning it as a physical table name. Every in-process verb
(`find`, `findOne`, `count`, `aggregate`, `insert`, `insertMany`,
`update`,
`delete`, `validate`) resolves through it, so all refuse uniformly: no
spelling allow-list, no per-caller marker. `judgeFilter` keeps judging
the
filter for an unresolved name (it reads nothing and reaches no driver),
as its
contract states.
- **Three platform-internal, constant-name best-effort probes** that
read a
known system object and were already fail-soft on a missing table now
treat
the engine's refusal (attributed to their own object) as the same "not
provisioned in this composition" case. A body cannot reach these paths:
`ObjectQL.probeInstallOrganizations` (registry-presence guard),
`SeedLoaderService.resolveSoleOrganizationId` and
`SysMetadataRepository`'s
history counters (refusal recognised by `code` and `object`).
- **`packages/spec`.** The `IObjectQLEngine.judgeFilter` docblock states
that
execution refuses an unknown object before admission (comment only; it
ships
in the built type declarations).
## Why (classes, doors, roles, codes only)
An action body invoked through the actions door could name a protected
member
of the stored-metadata family by a spelling the registry does not
resolve and
receive its stored content, whether a member or an administrator invoked
it.
The in-process verb handed that name to the driver as a raw table name,
and
every name-keyed in-process guard (PR #21513's reader seam among them)
was
addressed by the registered name only. The generic data door answers
`OBJECT_NOT_FOUND` for the same name. The engine now answers the same,
so the
two doors share one name space and no name-keyed guard can be stepped
around by
naming its target some other way.
## Census: does any legitimate platform reader rely on the raw-table
fall-through?
Instrumented the resolver's fall-through and ran the `objectql`,
`metadata-protocol` and `runtime` suites, plus a full boot, seed and
door drive
of four example apps (crm, showcase, todo, multi-package). The
instrument was
reverted in the branch; the net diff carries none of it.
| composition | fall-through reads | reader relies on it? |
|:--|:--|:--|
| 4 example apps booted, seeded, driven through the doors | **0** | no:
every platform reader addresses a registered object |
| unit/integration suites (partial registries + tolerant stub drivers) |
many | only test harnesses, plus the three constant-name probes below |
Every in-repo caller that passes a possibly-unresolved name, by
function:
| caller | object (constant) | pre-change | disposition |
|:--|:--|:--|:--|
| `ObjectQL.probeInstallOrganizations` | the org object | fail-soft on
missing table | moved: registry-presence, empty answer |
| `SeedLoaderService.resolveSoleOrganizationId` | the org object |
fail-soft on missing table | moved: recognise the refusal as
not-provisioned |
| `SysMetadataRepository` history counters | the history object |
fail-soft on missing table | moved: recognise the refusal as
not-provisioned |
| `ObjectQL.cascadeDeleteRelations` / `planCascadeAtomicity` /
`referenceExists` | a relation ref | already `try/catch` | unchanged:
already tolerate a throw |
| the data door's existence gate | the requested name | raised the 404
itself | now raises the shared factory |
Conclusion: **no production or example reader relies on the
fall-through.**
## Merge-queue fix
`os migrate account-issuer` reads `sys_account` through the driver the
engine routes that name to. Its read-only boot registers no
`sys_account`, and the engine now refuses an unregistered name. The
refusal is not read as absence, because that would report a table full
of accounts as a clean pre-flight. #21570's missing-table reading for
`sys_account` is kept, and every other failure still throws.
## Fixture triage (the test-only fallout, per the seat's answer)
Every test that encoded the raw-table fall-through, by disposition. No
ADR text
is edited, and no pin ruled under #7929 (the cross-field withholding
decision)
changes what it asserts. The table-keyed `captureExpectedReadRefusals`
noise
pins keep their subject and reshape their reading (item 3).
1. **The unregistered name is the deliberate probe: the test now asserts
the
refusal** (`code` + `status`, and where the test watched the driver,
that
the driver saw nothing).
- `objectql`: `engine-20822-no-field-map-type-blind-lowering`,
`query-expression-conformance`, `engine.test`,
`engine-undeclared-update-field`, `engine-undeclared-field-preflight`,
`engine-temporal-comparand-door`, `engine-aggregate-filter` /
`-having` / `-reference-verdict`, `engine-summary-recompute-context`,
`registry-field-type-refused-at-door`, the `global-search-*` pins,
`engine-judge-filter`, `engine-organization-probe-outage`.
- `protocol-unregistered-object.test.ts`, case B of the card 3770 gate:
the
door's 404 assertion is unchanged; the engine assertion turns from
"serves the row" to "refuses `OBJECT_NOT_FOUND` / 404"; header item ②
gains one sentence naming #21516.
- dogfood `registry-gate-wiring`: the premise reads ground truth at the
driver (host code's declared internal path), then asserts the engine
refuses the same name.
2. **The fall-through was incidental: the harness now registers what the
platform reader resolves** (registered after boot or DDL, so nothing new
is
provisioned and every outage/absence subject keeps its meaning).
- `objectql` metadata-write harnesses (delete, save, publish-meta,
publish-package-drafts, protocol-derived-provenance,
protocol-save-meta-repo-path, protocol-picklist,
protocol-publish-canonical-fold): the stored-metadata family.
- `rest` (14 harness files): the stored-metadata family, after DDL.
- `plugin-security` (4 files) and the `http-conformance` stack: the
authz
resolver's read set, unprovisioned, so the missing-table answer is still
what they measure.
- `plugin-approvals` status-mirror cascade: the delegation object and
the
org object, unprovisioned. `service-settings`: the secret and
setting-audit objects.
3. **Noise pins: same subject, reshaped reading.** The org probe for an
unregistered org object is now refused before any driver, so a pin that
read
"the probe reached the driver and was withheld" now reads "the probe
reached
no driver" (`tablesSeen()` equal to empty where `silentChannels()` was
read). `runtime` (about 17 files) and `trigger-record-change`.
`expected-read-refusal-noise.channel-asymmetry.test.ts` registers its
probe
object, unprovisioned, so the real driver refusal is still what its two
channels measure.
4. **`cli` served-boot control** (`schema-migrate.host-composition`):
each
hook's probe read is witnessed by its recorded `OBJECT_NOT_FOUND`
answer, not
by a driver line; the SQL driver suppresses that line for its own
deferred
set, so the line never was the subject.
5. `engine.test.ts`: one mock parameter typed (`name: string`), the
`@objectstack/objectql#typecheck` red of the earlier heads.
## New pin: the measured public door
`packages/runtime/src/unresolved-object-name.actions-door.pin.test.ts`
boots
the plugin set `bootStack` uses and drives REST `/actions`. The target
is a
table that exists and holds a sentinel row, created out of band at the
driver
and registered nowhere (the class the card measured, naming no protected
table). For an administrator and a member, the action body's read
answers
`404 OBJECT_NOT_FOUND` and the sentinel appears nowhere in the answer.
Control:
the same body shape on a registered name is served. Reference: the
generic data
door's answer for the same name is the same 404. PR #21513's reader-seam
pins
stay green.
## Ablation (one-shot; nothing left in the tree)
Mutation leg, `scripts/ablation-replace.mjs` on `engine.ts`: the refusal
in
`resolveObjectName` replaced by the old raw-table return plus a marker
branch
(marker on disk 1, refusal on disk 0); rebuilt;
`ablation-dist-preflight`:
marker present in 4 built files. Pins under mutation:
- objectql (`protocol-unregistered-object`, `engine-20822-...`,
`query-expression-conformance`, `engine-judge-filter`):
`5 failed | 245 passed (250)`
- runtime actions-door pin: `2 failed | 4 passed (6)` (both role cases)
Restore leg: `git checkout HEAD -- engine.ts`; blob equals the HEAD blob
`f5793bff919d`, `git diff HEAD` empty, porcelain clean; rebuilt; marker
absent
from all 14 built files. Pins restored: `250 passed (250)`, `6 passed
(6)`.
`engine.ts` is byte-identical on the final head (the later merge of
`main`
carried no `objectql` source).
## Verification on the final head `6752a29827` (merge of `origin/main`
at `1ca1eb0972`)
- `objectql` full suite: `367 files, 7384 passed`.
- `metadata-protocol` full suite: `206 passed, 3 skipped files; 3187
passed, 19 skipped`.
- `runtime` full suite: `317 files; 5176 passed, 19 skipped`.
- `service-analytics` full suite: `175 files; 4152 passed, 253 skipped`.
- `cli` unit tier: `252 files, 3685 passed`; integration tier, the three
files
this branch or the merged `main` touched: `36 passed`.
- `mcp`: `35 files, 389 passed`; dogfood (`registry-gate-wiring` + the
two
files `main` added): `16 passed`; the `main`-added example, `metadata`
and
`core` files: green.
- `plugin-security`, `plugin-approvals`, `service-settings`,
`http-conformance`, `trigger-record-change` test tasks: turbo `38/38`
(5 test tasks run, 33 cached builds).
- Build closure for the above: `63/63` turbo tasks.
- Before the merge (head `bab0903840`): typecheck of every touched
package
`76/76` tasks; `rest` repo project `177 passed`; the 14 `rest` harness
files
`552 passed, 21 skipped`.
- CI on `6752a29827`: every check green except "Part-of PR must not also
close
its card", which read the earlier body; this body is its input.
## Acceptance notes
- Recorded decision of card 3770 ("the engine deliberately does not
reject;
internal callers unaffected") is narrowed at the engine: the door's gate
is
unchanged and the engine now gives the same answer. ADR-0053's
type-blind
lowering is kept for a registered object with no field map, and removed
for an
unregistered name (the bypass itself). No ADR text is edited here.
- Changesets: `@objectstack/core` minor (`Clause-②: yes`, the new
export);
`@objectstack/objectql` minor (`Clause-②: no (narrowing)`, with its
ADR-0087
disposition); `@objectstack/metadata-protocol` patch;
`@objectstack/spec`
patch (docblock).
- `packages/cli/test/refusal-renders-once.e2e.test.ts` (added on `main`)
sits
in neither of the cli package's two vitest projects and was not run
here;
it drives refusal rendering of `os init` / `os compile`, which this
change
does not reach.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 35dfb81 commit eb9ef79
81 files changed
Lines changed: 1102 additions & 223 deletions
File tree
- .changeset
- packages
- cli/src
- commands/migrate
- utils
- core/src
- utils
- metadata-protocol/src
- objectql/src
- plugins
- plugin-approvals/src
- plugin-security/src
- qa
- dogfood/test
- http-conformance/src
- rest/src
- runtime/src
- sandbox
- services/service-settings/src
- spec/src/contracts
- triggers/trigger-record-change/src
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
20 | 28 | | |
21 | 29 | | |
22 | 30 | | |
| |||
126 | 134 | | |
127 | 135 | | |
128 | 136 | | |
129 | | - | |
| 137 | + | |
130 | 138 | | |
131 | 139 | | |
132 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
133 | 153 | | |
134 | 154 | | |
135 | | - | |
136 | | - | |
| 155 | + | |
| 156 | + | |
137 | 157 | | |
138 | | - | |
139 | | - | |
140 | | - | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
141 | 162 | | |
142 | | - | |
143 | | - | |
144 | | - | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
145 | 171 | | |
146 | | - | |
| 172 | + | |
147 | 173 | | |
148 | | - | |
| 174 | + | |
149 | 175 | | |
150 | 176 | | |
151 | 177 | | |
| |||
Lines changed: 9 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
908 | 908 | | |
909 | 909 | | |
910 | 910 | | |
911 | | - | |
| 911 | + | |
| 912 | + | |
912 | 913 | | |
913 | 914 | | |
914 | 915 | | |
| |||
963 | 964 | | |
964 | 965 | | |
965 | 966 | | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
966 | 972 | | |
967 | | - | |
| 973 | + | |
968 | 974 | | |
| 975 | + | |
969 | 976 | | |
970 | 977 | | |
971 | 978 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
158 | 163 | | |
159 | 164 | | |
160 | 165 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
10371 | 10371 | | |
10372 | 10372 | | |
10373 | 10373 | | |
10374 | | - | |
10375 | | - | |
10376 | | - | |
10377 | | - | |
10378 | | - | |
10379 | | - | |
10380 | | - | |
10381 | | - | |
10382 | | - | |
| 10374 | + | |
| 10375 | + | |
| 10376 | + | |
| 10377 | + | |
| 10378 | + | |
| 10379 | + | |
| 10380 | + | |
| 10381 | + | |
10383 | 10382 | | |
10384 | 10383 | | |
10385 | | - | |
10386 | | - | |
10387 | | - | |
10388 | | - | |
| 10384 | + | |
| 10385 | + | |
| 10386 | + | |
| 10387 | + | |
| 10388 | + | |
| 10389 | + | |
| 10390 | + | |
| 10391 | + | |
10389 | 10392 | | |
10390 | 10393 | | |
10391 | 10394 | | |
| |||
10474 | 10477 | | |
10475 | 10478 | | |
10476 | 10479 | | |
10477 | | - | |
10478 | | - | |
10479 | | - | |
10480 | | - | |
10481 | | - | |
| 10480 | + | |
| 10481 | + | |
| 10482 | + | |
10482 | 10483 | | |
10483 | 10484 | | |
10484 | 10485 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1649 | 1649 | | |
1650 | 1650 | | |
1651 | 1651 | | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
1652 | 1662 | | |
1653 | 1663 | | |
1654 | 1664 | | |
1655 | 1665 | | |
1656 | 1666 | | |
1657 | | - | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
1658 | 1670 | | |
1659 | 1671 | | |
1660 | 1672 | | |
| |||
0 commit comments