Repository navigation
Commit c74d843
Fixes #22739
Clause-②: yes (narrowing)
Census row 7 of #22661: the import door's reference resolution matched a
lookup cell against a TARGET object nobody addressed, without asking
that target its declared exposure. A match stored the target record's id
and a miss answered `reference_not_found` per row, so the row report
told the two apart even for a target every data route refuses.
## What changed
- **The decision.** `servesReferenceTarget` (new,
`packages/core/src/utils/import-runner.ts`) asks the spec's one exposure
decision, `canServeApiOperation` (`@objectstack/spec/data`), of the
target's own `enable` block, read through the protocol's `getMetaItem`.
`resolveRef` (the `RefResolver` that `runImport` builds) asks it first,
once per target per import. No second rule and no second list: the
decision function is the one #22661 settled.
- **The answer for a refused target** is the measured precedent for a
target the caller cannot read: every cell, naming a record or not, a
pasted id included, answers `reference_not_found`, and nothing of the
target is read. A declaration that cannot be read withholds too
(fail-closed). No new error code, no status change.
- **The operation is `list`.** Matching a cell is a predicate read over
the target (`findData` with a `where` on one candidate field), the
runtime `find` the spec maps to `list` in
`DATA_ACTION_TO_API_OPERATION`, and the same read a list route with a
field filter performs. `get` would keep serving a name match on a
get-only target, which refuses exactly that read; #22661's two reads
asked `get` because each turns an id the caller already holds into a
record.
- **The target is read through the arbiter, for a field that declares
`reference`.** `buildFieldMetaMap` (`import-field-meta.ts`) treats
exactly `main`'s set of fields as references (those carrying a
`reference` string), and reads such a field's target through
`referenceTargetOf`. So the #22661 enumeration pin now sees this caller
and classifies it `decided` (operation `list`, decision
`servesReferenceTarget`). A `user` field written without `reference` is
unchanged (REWORK 6105610630); its gap is carried by #22785.
- **`ImportProtocolLike`** gains one optional member, `getMetaItem`. A
protocol without it (plugin-auth's identity import, whose reads run
under the system identity) has no declaration to judge and is not
judged; none is fabricated.
The decision takes no caller, so an administrator, a member and a system
context (the connector pull, which drives `runImport` through the real
protocol) are answered the same, as #22661's two reads answer every
caller the same.
## Measured on a real stack (fixture objects only)
`@objectstack/verify` boot with the real SecurityPlugin, ObjectQL, SQL
driver and REST layers; the #22661 fixture targets plus a row-scoped
one; an administrator and a member; the synchronous import door and the
async jobs door; three cells per target (naming the record, naming none,
the record's id).
| target declaration | before (base `bf515e724d`) | after |
|---|---|---|
| off switch / whitelist without `list` (create-only, get-only);
deny-all pinned in the core unit test only | match and id stored, miss
`reference_not_found` | all three `reference_not_found` |
| no `enable` block / whitelist granting `list` | match and id stored,
miss `reference_not_found` | unchanged |
| PRECEDENT: member without read on the target, or record hidden by row
scope | all three `reference_not_found` | unchanged |
Identical for both personas and both doors (the member's job report was
read with a read grant on its own import jobs added to the fixture
member). The refused rows carry the precedent's exact sentence shape
(field label, then the cell). Before-readings reproduce #22661's census
row 7.
## Pins and ablation
- `packages/core/src/utils/import-runner-reference-exposure.test.ts`
(14): per persona, four refusing declarations answer match, miss and id
alike and never call `findData`; two serving declarations resolve
(controls); one fail-closed case; and the `user`-field control (a `user`
field without `reference` has no target and its cell reaches the write
unresolved, as on `main`).
- `packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts`
(11): per persona, an ARMED leg (each target's own list answer read off
the data door), then per door (sync and jobs) a withheld leg and a
served control; plus the precedent leg.
- `packages/core/src/security/second-object-read-exposure.pin.test.ts`
(4): the new `decided` row.
Ablation, each through `scripts/ablation-replace.mjs` on committed
`22bfc2770b`, anchors 1 to 0, each restore proven (blob equals HEAD,
`git diff HEAD` empty):
- A, wiring bypassed in `resolveRef`: core pin 9 red / 4 green (every
refusing leg plus fail-closed).
- A2, operation `get`: 4 red / 9 green (get-only refused legs and
list-only controls flip).
- A3, the catch fails open: 1 red (fail-closed).
- D, `import-field-meta.ts` back on the raw carrier: enumeration pin 2
red (the stale classification and the matcher leg).
- G (rework, on committed `f06912e442`), the guard removed (`reference:
referenceTargetOf(f)`): the `user`-field control 1 red / 13 green.
- C, A on the built artifact: core rebuilt, `ablation-dist-preflight`
marker present (exit 0), dogfood pin 4 red / 7 green (the withheld leg
of both doors for both personas); restore rebuilt, preflight `--absent`
exit 0, rerun 11 / 11.
## Verification
Each reading names the commit it was taken on. The final head is
`c06ec74a87`. The rework merged `origin/main` twice: `0d326bfb12`, then
`c06ec74a87`, because #22766 edited the enumeration pin. The decided
lists were united: `servesExpansionTarget`, `servesLabelTarget`,
`servesPayloadDisplayTarget`, `servesReferenceTarget`,
`servesSummaryTitleTarget`.
- `@objectstack/core` at `c06ec74a87`, after a full workspace build:
`test` 93 files / 2349 passed; `test:repo` 5 files / 55 passed;
`typecheck` exit 0, including `check:test-typecheck`.
- `@objectstack/dogfood` at `c06ec74a87`: this pin 11 / 11 and #22661's
`second-object-exposure` pin 25 / 25 (36 / 36); `typecheck` exit 0.
- Importers at `c06ec74a87`: `@objectstack/rest` `typecheck` exit 0, and
its 38 test files that reach the import, export or template doors 38 /
38; `@objectstack/plugin-auth` `admin-import-users` 2 files / 50;
`@objectstack/service-automation` connector pull 3 files / 22.
- Lint, narrowed and proven: `eslint --no-inline-config` on the 6
changed `.ts` files; population read from `--print-config`; `--format
json` 6 files, 0 errors, 0 warnings. `eslint.config.mjs` enables no
type-aware linting. Repo-wide `pnpm lint` is CI's.
- Gates at `c06ec74a87`: `dispatch-gates --commands` derived 69 (the
dispatch's 51 are a subset); all 69 exit 0; `--ran`: 69 derived, 69 run,
0 NOT-MEASURED, 0 UNRUN.
## File surface
- `packages/rest/src/export-format.test.ts` is outside the claim's
surface. Its presentation-keys fixture declared `reference` on a
`number` field; read through the arbiter a non-reference type names no
target, so the fixture now declares a `lookup` (the fixture declared a
key that is inert on a `number` field (`FieldSchema` has no per-type
refinement on `reference`)). Test-only, two literals.
- The door-level pin reuses the #22661 fixture without editing it; the
member read grant on its import jobs is added in the new test file.
## Acceptance notes
- **`Clause-②: yes (narrowing)`** (edited by the seat after the contract
review 6106439614): no accept set widens, but `ImportProtocolLike`,
exported from `@objectstack/core`, gains one optional member
(`getMetaItem?`). That is an additive, type-level public-surface change,
spelled `yes` by the fleet's practice. The changeset's line follows on
this PR's next head.
- **`user` fields written without `reference`.** The field set the
import, export and template doors treat as references is exactly
`main`'s, so such a field is unchanged here, and pinned. The door
disagrees with the spec's arbiter, which gives it `sys_user`. That gap
is measured on `main` and carried by #22785, because fixing it is a
Clause-② widening.
- **The residual narrowing: a non-reference type that declares
`reference`.** Read through the arbiter, it names no target. The only
population whose answer moves is the legacy, schema-refused `type:
'reference'` spelling, which is still listed in the doors' own type
tables. An AST census over every git-tracked non-test source under
`packages/` and `examples/` (3,636 files at `0d326bfb12`, the same at
`c06ec74a87`) found 0 shipped field declaring it. Controls: 30
reference-typed literals and 80
`Field.lookup`/`masterDetail`/`user`/`tree` calls. Positive control:
three planted shapes were all found. The changeset states it in FROM →
TO.
- **Measured producers.** On `origin/main` `bf515e724d`, twelve in-repo
objects refuse `list` by declaration and exactly one lookup points into
any of them, from an object that is itself `apiEnabled: false`; control:
75 lookups into `sys_user`. No shipped object's import answer changes.
- `toFailedResult` is untouched; #22741 follows in this file.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8bd0fcd commit c74d843
7 files changed
Lines changed: 367 additions & 21 deletions
File tree
- .changeset
- packages
- core/src
- security
- utils
- qa/dogfood/test
- rest/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 | + | |
Lines changed: 18 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
15 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
48 | 44 | | |
49 | 45 | | |
50 | 46 | | |
| |||
79 | 75 | | |
80 | 76 | | |
81 | 77 | | |
82 | | - | |
| 78 | + | |
83 | 79 | | |
84 | 80 | | |
85 | 81 | | |
| |||
132 | 128 | | |
133 | 129 | | |
134 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
135 | 139 | | |
136 | 140 | | |
137 | 141 | | |
| |||
288 | 292 | | |
289 | 293 | | |
290 | 294 | | |
| 295 | + | |
291 | 296 | | |
292 | 297 | | |
293 | 298 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
18 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
19 | 27 | | |
20 | 28 | | |
21 | 29 | | |
| |||
79 | 87 | | |
80 | 88 | | |
81 | 89 | | |
82 | | - | |
| 90 | + | |
83 | 91 | | |
84 | 92 | | |
85 | 93 | | |
| |||
Lines changed: 122 additions & 0 deletions
| 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 | + | |
0 commit comments