Repository navigation
Commit fe0ec2b
fix(spec)!: refuse an object's
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wg6zwDuV2XvoDkqC7bXgqnpc)_
Fixes #25168
Clause-②: no (narrowing)
Metadata-protocol: narrowed
## What
ADR-0015 §4.2 allows `object.external` only on a datasource whose
`schemaMode` is not `'managed'`, and declares the rule "enforced at
metadata-load time". No load door enforced it. This PR makes the spec
judge it, once, through one predicate
(`packages/spec/src/data/external-binding-datasource.ts`, internal), at
each door that can see the halves it needs. This follows the route in
triage comment 6333828054: a declaration rule at the parse, inherited by
the save door and `os validate`, and no second check in
`packages/objectql`.
| The object's `datasource` | Where it is judged | Answer now |
|---|---|---|
| omitted or `'default'` (the primary, managed by construction) |
`ObjectSchema`'s parse (superRefine), at `external` |
`ObjectSchema.create` throws at module load. `defineStack` and `os
validate` give `STACK_SCHEMA_INVALID` (422) at `objects.N.external`.
`PUT /api/v1/meta/object/NAME` gives `422 INVALID_METADATA` with the
finding at `external`, and nothing is stored. |
| a datasource the same stack declares with `schemaMode: 'managed'` or
with no `schemaMode` | `defineStack`'s cross-reference pass
(`validateCrossReferences`) | `STACK_CROSS_REFERENCE_INVALID` (422), one
finding per bound object. `os validate` and `os build` inherit it. |
| a datasource declared `schemaMode: 'external'` or `'validate-only'` |
(both doors) | accepted, as before |
| a datasource the stack does not declare | no door can see its
`schemaMode` | no verdict, as before (see Acceptance notes) |
A package `defaultDatasource` or a `datasourceMapping` rule does not
count as the object's datasource. They route queries, while the boot
validation (`validateObjectUsing`) and the write gate
(`assertWriteAllowed`) read only the object's own `datasource`. The
refusal message says so.
ADR-0087 kit: the semantic entry
`object-external-binding-managed-datasource-refused`
(`packages/spec/src/migrations/entries/semantic/`), and a
`REFUSED_STORED_SHAPES` detector that lists each stored object in the
refused shape for `os migrate meta --stored` (no lossless rewrite:
dropping `external` would turn the remote table into one ObjectStack
creates). The changeset carries the `registered` disposition, `minor`,
BREAKING with FROM and TO, and the one-line fix.
## Measured on the base (b6e8707), before any edit
- `defineStack` accepted all six probe stacks: datasource omitted,
`'default'`, declared `managed`, declared with no `schemaMode`, declared
`external` (control), and undeclared.
- `os validate` answered `valid: true`, exit 0, for both a
declared-`managed` and a no-`schemaMode` datasource. This was the card's
first NOT MEASURED reading.
- The object save door stored all three probes (datasource omitted,
`'default'`, and a stored managed datasource named `wh`) and answered
success. Each save logged the platform's own "published but its storage
was NOT created" warning. This was the card's second NOT MEASURED
reading.
## After this PR (same probes)
- `defineStack`: managed and no-`schemaMode` give
`STACK_CROSS_REFERENCE_INVALID` 422. Omitted and `'default'` give
`STACK_SCHEMA_INVALID` 422. The `external` control and the undeclared
datasource are accepted.
- `os validate`: exit 1, `valid: false`, with those codes and messages.
- Save door: `422 INVALID_METADATA` at `external` for omitted and
`'default'`, and nothing is stored. A named stored datasource still
saves; see Acceptance notes.
## Producers (the stop condition)
- In the repo, outside tests and including `examples/**`, the only
object with `external` is the showcase's, and it is compliant:
`showcase_external` is declared `schemaMode: 'external'`.
- Templates, CLI scaffolds and published skills have none.
- The Sync/import producer is gone since #25167, which was already
merged when this branch was cut.
- HotCRM at c8a7a7047 has none.
No producer was found, so no conversion question applies.
## Tests
- `packages/spec/src/data/external-binding-datasource.test.ts`: the
parse refuses at `['external']` (code `custom`) for datasource omitted,
`'default'`, and an empty binding. `ObjectSchema.create` throws with the
same issue. Controls and the predicate's unit cases.
- `packages/spec/src/stack-external-binding-datasource.test.ts`: `code`
+ `status` for both stack verdicts, one finding per bound object, the
`external` and `validate-only` controls, an undeclared datasource
accepted, `strict: false` skips, and a manifest `defaultDatasource` does
not count.
- `packages/spec/src/conversions/refused-shapes.test.ts`: the stored-row
detector lists each object.
-
`packages/metadata-protocol/src/protocol.external-binding-primary-datasource-refusal.test.ts`:
the save door answers `INVALID_METADATA` 422, issues at `external`, and
stores nothing; controls save.
- `packages/spec/src/data/object-strictness-batch20.test.ts`: four
fixtures bound `external` with no datasource. They now name their
federated datasource (`remote_crm`), per fixture triage (add the missing
declaration).
Ablation, one time, on 488c2fe:
- The mutation made the predicate answer nothing, which drops all three
halves at once. It was checked on disk (grep of the injected text in the
source, and in 12 built `.mjs` files), and `@objectstack/spec` was
rebuilt.
- Under the mutation, the three spec files went red, 16 failed of 76,
and the save-door file went red, 2 failed of 4 (exactly the two refusal
cases).
- The source was restored with `git checkout HEAD`, and the restore was
proved by comparing blob hashes (equal to HEAD) and by an empty `git
diff HEAD`. `@objectstack/spec` was then rebuilt with declarations, and
the mutation is in 0 dist files.
- After the restore, the spec files pass 76 of 76 and the save-door file
passes 4 of 4.
Full `@objectstack/spec` suite on 03234e2: 780 files passed and 1
skipped; 22737 tests passed, 1 skipped, 1 todo. The next commit,
488c2fe, only narrows the assertions in two of the new test files, and
those files pass 76 of 76 on it.
## Acceptance notes
- **The save door cannot see a named datasource's `schemaMode`.** `PUT
/api/v1/meta/object/NAME` with `datasource: 'wh'`, where `wh` is a
stored managed datasource, plus `external`, still saves (measured on
this head). The spec parse sees only the object. The minimal place is
the metadata-protocol object save door, calling this same predicate with
the datasource it resolves, so the rule is not copied. That needs the
predicate exported and is outside this card's file surface. Raised in
the report as an open question.
- **Not covered here, also noted in the report:** the `composeStacks`
artifact pass (it does not re-run `validateCrossReferences`); the
reverse direction (saving a datasource as `managed` while objects bind
it with `external`); and the `/* ... */` example in `content/docs`
external-datasources §5, which should show the object's `datasource`.
- **Runtime fixtures not rewritten.** 62 fixtures in 37 test files
across 12 packages hand an object with `external` and no datasource
straight to a driver, the engine or a lint rule, without the parse.
Those suites are green on this head. They describe a shape the parse now
refuses, but they test runtime behaviour that a stored legacy row can
still reach: such a row still registers under `[metadata_spec_invalid]`.
They were left as they are.
- **File surface.** `scripts/engine-double-contract.pinned.json` gains
15 rows from `node scripts/check-engine-double-contract.mjs --write`,
for the save-door test's engine double. It is a mechanical ledger row,
as in #25181 and #25122.
- **Branch name.** The branch is the session's designated
`claude/project-thread-5h4pzn`, not `claude/issue-25168-…`. The claim
names it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013dX1BHfcVSJNSkfA6cvwJN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>external binding on a managed datasource (ADR-0015 §4.2) (#25192)1 parent 27dc53b commit fe0ec2b
12 files changed
Lines changed: 717 additions & 9 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- spec/src
- conversions
- data
- migrations/entries/semantic
- scripts
| 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 | + | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
64 | 65 | | |
65 | 66 | | |
66 | 67 | | |
| |||
470 | 471 | | |
471 | 472 | | |
472 | 473 | | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| |||
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
| 58 | + | |
56 | 59 | | |
57 | 60 | | |
58 | 61 | | |
| |||
175 | 178 | | |
176 | 179 | | |
177 | 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 | + | |
178 | 218 | | |
179 | 219 | | |
180 | 220 | | |
| |||
361 | 401 | | |
362 | 402 | | |
363 | 403 | | |
| 404 | + | |
364 | 405 | | |
365 | 406 | | |
366 | 407 | | |
| |||
| 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 | + | |
0 commit comments