Repository navigation
Commit b7cd1af
Fixes #22785
Clause-②: yes (narrowing)
## What changes
`buildFieldMetaMap` (`packages/core/src/utils/import-field-meta.ts`) now
asks the spec's arbiter `referenceTargetOf` both WHETHER a field is a
reference and WHAT its target is. Before, only a field carrying a
`reference` string reached the arbiter. So a `user` field written
without `reference` was not a reference at the import, export and
import-template doors, although the arbiter (and the data door's
`$expand`) reads it as `sys_user`.
- No second list of implicit targets: the arbiter's own
`IMPLICIT_REFERENCE_TARGETS` is the only one.
- A `reference` key that is present but not a string still stays out of
the arbiter, which throws on it. Such a field names no target, as
before.
- The #22739 narrowing stands: a non-reference type declaring
`reference` still names no target.
- Every newly resolved target is asked its exposure first
(`servesReferenceTarget`, #22739). No code path bypasses it.
- Source: +9 / -6 in one file. No source change in `packages/rest`, in
`import-runner.ts` (`toFailedResult` untouched) or in `packages/types`.
## Door readings, before and after
Real boot through the dogfood harness (SecurityPlugin, ObjectQL, SQL
driver, REST). Base `9f5eca52b3` against the fix `ce23f40ae8`, with
`@objectstack/core` rebuilt and its `dist` checked. The fixture object
has `owner` written as `{ type: 'user' }` (no `reference`) and
`assignee` written as `Field.user()`. The administrator and the member
answered identically, except on the hidden-user row.
| Door | Input | Before | After |
|---|---|---|---|
| `POST /data/:object/import` and `/import/jobs` | `owner` = a user's
email | `reference_not_found` ("no sys_user record has id ..."), nothing
stored | stores the user's id |
| same | CONTROL `assignee` (Field.user) = the same email | stores the
id | stores the id |
| same | `owner` = a visible user's id | stores | stores |
| same | `owner` = an unknown email | `reference_not_found` (the write's
sentence) | `reference_not_found` ("no record matches ...") |
| same, member | `owner` = the id of a user outside the member's
organizations (the member's `GET /data/sys_user/ID` answers 404) |
stores | `reference_not_found`; CONTROL `assignee` answers
`reference_not_found` in both |
| `GET /data/:object/export` (JSON and CSV) | `owner` holding the
admin's id | the raw id | the user's name ("Dev Admin"), same as
`assignee` |
| `GET /data/:object/export?template=true` | `owner` instructions row |
"The name of a record, or its id." | "The name of a User record, or its
id." |
| connector pull (`pullConnectorSource`, stub protocol, built core) |
`owner_email` mapped to `owner` | the raw email written as the id (ok) |
the user's id written; an unknown email is `reference_not_found` |
| plugin-auth identity import | `sys_user`'s served map (26 fields) |
`manager_id` to `sys_user`, `primary_business_unit_id` to
`sys_business_unit`; 0 reference-typed fields without `reference` |
identical |
| data door `POST /data/:object` (not this change) | `owner` = the
hidden user's id | 201 | 201 |
- The connector-pull before reading and the hidden-user before reading
were taken under ablation A1 below: main's guard restored and core
rebuilt.
- Two rows are a narrowing or a change to published output, and the
changeset names both under its BREAKING banner: the hidden-user import
row (stored before, refused now) and the export column (the stored id
before, the user's name now). Each moves to the answer a `Field.user`
field already gives.
## Pins
- **core** `import-runner-reference-exposure.test.ts`: the #22739
control block (`owner` with no target) is replaced by four cases.
- The map names `sys_user` for both user fields, and no target for a
non-reference type or for a non-string carrier.
- An email in the field without `reference` stores the user's id.
- CONTROL: `Field.user` does the same.
- CONTROL: an unserved `sys_user` (`apiEnabled: false`) refuses both
cells, and `findData` is never called.
- **rest** `export-format.test.ts`: `referenceFieldNames` includes the
field without `reference`. `import-template.test.ts`: its instructions
name `User`.
- **dogfood** `import-user-field-target.dogfood.test.ts`: the sync and
jobs doors for `owner` and for the `Field.user` control, plus the export
door.
## Ablations
Each ablation ran on a committed tree through
`scripts/ablation-replace.mjs` (WRAP mode). Each restore was proven:
blob equals HEAD and `git diff HEAD` is empty.
- **A1, the fix reverted** (delete `f.reference == null || `). Core
rebuilt; `ablation-dist-preflight.mjs --absent` green over 14 built
files. Before the mutation, the fixed build carried the marker in
`dist/index.js` (count 1).
- core: 3 red / 14 green. Both `Field.user` cases and the 13 #22739 pins
stay green.
- rest: 2 red / 78 green.
- dogfood: 3 red / 2 green. Both `Field.user` legs stay green.
- Restore leg: core rebuilt, preflight presence green (2 built files),
tree clean, everything green.
- **A2, the exposure ask removed** (`if (!(await served)) return {};` in
`import-runner.ts`): 10 red / 7 green. The red cases are the 9 #22739
withheld legs and the new unserved-`sys_user` control, so that control
is #22739's refusal.
- **A3, the non-string guard removed** (the ternary replaced by
`(referenceTargetOf(f))`): 4 red / 13 green. Each red is the arbiter's
TypeError on the non-string carrier.
- The first A1 attempt sent nothing: the tool refused it because the
replacement was a substring of the anchor, so its count could not rise.
It was re-run as a delete.
## Verification
The package suites ran at `4b78f72c5f`. The next merge, to `272f15bd62`
(`origin/main` `098481744f`), brought only `plugin-webhooks`, the
lockfile and `scripts/engine-double-contract.pinned.json`. The dogfood
pins were re-run at `272f15bd62`.
- core: `test` 93 files / 2352 passed; `test:repo` 5 / 55, the
enumeration pin `second-object-read-exposure.pin.test.ts` included;
`typecheck` exit 0 (`check:test-typecheck` OK).
- rest: the 38 test files reaching the import, export and template
doors: 1225 passed, 22 skipped. `typecheck` exit 0.
- plugin-auth: `admin-import-users` 2 files / 50. service-automation:
`connector-pull` 3 files / 22.
- dogfood: `typecheck` exit 0. The two pins (this one and #22739's
`import-reference-exposure`) passed 16 / 16 at `272f15bd62`.
- Each new or edited test file is in its package's tsc program
(`--listFiles`).
- **Gates at `272f15bd62`.**
- `dispatch-gates --commands` derived 68 gates; the dispatch's 50 are a
subset. All 68 ran, and each exited 0.
- `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET (8
packages had no `dist`). After those packages were built it exited 0.
- `--ran` reconcile: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN.
- **Lint, narrowed, at `272f15bd62`.** `eslint --no-inline-config` over
the 5 changed `.ts` files.
- Population from `--print-config`: 5 to 6 active rules each, none
ignored.
- `--format json`: 5 files, 0 errors, 0 warnings.
- `eslint.config.mjs` enables no type-aware linting, so no untouched
file's verdict can move. Repo-wide lint belongs to CI.
- **Round 2 (changeset text only, head `7fffd20e84`, no code change).**
`check-adr-0087-registration --base origin/main` 0
(`[BREAKING+bang+clause-②-narrowing] not-required
(no-migration-prescription)`); `check-changeset-no-major --base
origin/main` 0; `check-empty-changeset --base origin/main` 0;
`check:changeset-gate-self-tests` 0; `check:nul-bytes`,
`check:issue-citations` (both spellings), `check:doc-authoring`,
`check:pm-changeset-deadline-census`, `check:objectui-changeset` 0.
Re-derived `dispatch-gates --commands`: the same 68 gates as above.
- The changeset's per-door section is now labelled "Door by door, before
and after". The gate read the old label, "FROM → TO", as a migration
prescription, which contradicted `no-migration-prescription`. The
bullets are unchanged.
- `origin/main` has since moved to `23419bafb7`. Its one file
overlapping this branch's reads, the enumeration pin, changes a
different row (the relation-filter one). `git merge-tree` is clean, so
the merge is left to CI and the queue.
## Acceptance notes
- **Clause-② arm.** The seat ruled the arm `yes (narrowing)`: the
hidden-user id moves from stored to refused, and the export column moves
from the stored id to the user's name. The changeset therefore carries
`fix(core)!`, the `**BREAKING**` banner naming both populations, and the
ADR-0087 marker `not-required (no-migration-prescription)`. It also adds
a one-line fix for a reader that needs the stored id: the record read
(`GET /api/v1/data/:object`, without `expand`).
- `buildFieldMetaMap` serves the export and template doors from
`packages/rest`, so their answers moved with no `packages/rest` source
change. They are pinned by test only.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5fdc1a8 commit b7cd1af
6 files changed
Lines changed: 223 additions & 29 deletions
File tree
- .changeset
- packages
- core/src/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 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
90 | | - | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
91 | 94 | | |
92 | 95 | | |
93 | 96 | | |
| |||
Lines changed: 42 additions & 23 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
99 | 100 | | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
111 | 112 | | |
112 | 113 | | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
117 | 133 | | |
118 | | - | |
119 | | - | |
120 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
121 | 140 | | |
122 | 141 | | |
Lines changed: 115 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
296 | 297 | | |
297 | 298 | | |
298 | 299 | | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
440 | 440 | | |
441 | 441 | | |
442 | 442 | | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
443 | 449 | | |
444 | 450 | | |
445 | 451 | | |
| |||
0 commit comments