Repository navigation
Commit 5fd2c76
feat(spec)!: retire seven overdue ADR-0087 D2 conversions from the load path at 18; record why eight stay (#23523)
Fixes #23487
Clause-②: no (narrowing)
## What
The family closing card for ADR-0087 D2 conversions that were still on
the load path at 18 although their window had closed. Every entry now
has a verdict: seven are retired from the load path, and eight stay,
each with the reason written into its own docblock, including what would
have to change for it to retire.
**Retired** (`retiredFromLoadPath: true`, `retiredAfter: '17.7.0'`, the
newest published stable release under the `18.0.0-next` label, the same
stamp PR #23485 used):
| entry | `toMajor` | refusal an author now meets |
|:--|:--|:--|
| `flow-node-crud-object-alias` | 17 | CRUD node contract, at
`config.object`, naming `objectName` |
| `flow-node-crud-filter-alias` | 11 | CRUD node contract, at
`config.filters`, naming `filter` |
| `flow-node-notify-config-aliases` | 17 | `notify` contract, at `to` /
`subject` / `body` / `url` / nested `source` |
| `flow-node-map-flow-alias` | 17 | `map` contract, at `config.flow`,
naming `flowName` |
| `flow-node-subflow-flow-alias` | 17 | `subflow` contract, at
`config.flow`, naming `flowName` |
| `flow-node-script-config-aliases` | 17 | `script` contract, at
`functionName` / `input`, naming `function` / `inputs` |
| `sharing-rule-access-level-full-to-edit` | 17 | `SharingLevel`, now an
`enumWithRetiredValues` whose `full` prescription names `edit` |
Every one of those refusals is now the upgrade prescription: it names
the canonical key, the release that refuses the old spelling
(`@objectstack/spec 18`), and, for the step-17 entries, `os migrate meta
--from 16`. The flow-node prescriptions used to say "the conversion
rewrites it at load", which becomes false at authoring once the entry
retires. `accessLevel: 'full'` used to get zod's bare `Invalid value
'full'. Expected one of: read, edit.` Each entry has a row in the
removal-version pin's `JOIN`
(`retired-key-removal-version.pin.test.ts`), so the release each
prescription names is derived from the registry, not trusted.
**Kept, with the reason recorded in the docblock:**
| entry | why it stays (measured on this tree) |
|:--|:--|
| `flow-node-http-callout-rename` (11) | `flow.node.type` is an open
namespace (ADR-0018): retired, `type: 'http_request'` passes
`defineStack` silently. Retiring it needs a decision to reserve retired
official names. |
| `page-kind-jsx-to-html` (11) | `'jsx'` is still a declared member of
`PageSchema.kind` and of `COMPILED_PAGE_KINDS`: retired, it passes
silently. |
| `view-visibleOn-to-visibleWhen` (15) | `visibleOn` is still a declared
`[DEPRECATED]` key that the form schemas fold into `visibleWhen` at
parse, so retiring the entry alone changes nothing an author can
observe. It retires with the alias (a `retiredKey` tombstone). |
| `page-component-visibility-to-visibleWhen` (15) | Same, on
`PageComponentSchema.visibility`. |
| `flow-node-wait-event-config-lift` (17) | `wait` has no `config`
contract: retired, a loose `config.timerDuration` beside a declared
`waitEventConfig` passes silently. Refusing loose keys would also refuse
stored flows whose loose key the block shadows (the rehydration seam
parses the same schema), which needs a ruling. |
| `flow-node-connector-config-lift` (17) | Same shape: a loose
`config.input` beside a complete `connectorConfig` passes silently, and
the action would dispatch with no input. |
| `datasource-driver-mongo-to-mongodb` (17) | `'mongo'` is still a legal
value, so there is no refusal to hand the author to. Needs a decision:
retire the value, or record the entry as permanent. |
| `retry-policy-converged` (17) | The job half alone. This entry writes
the pre-17 `maxRetries: 3` / `backoffMultiplier: 2` into a source
`job.retryPolicy` that omits them; retired, such a job would silently
drop from 3 retries to 0 under the merged defaults, a default flip no
gate can see. It can retire once that flip has a ruling for authored
sources. (The `retryDelayMs` rename is not a blocker: every surface
already refuses it, a `try_catch` node's `config.retry` included, by the
`RetryPolicySchema` tombstone. An earlier version of this row said
otherwise; that was a wrong reading taken from a node-level parse,
corrected at contract review.) |
`conversions.test.ts` holds the verdict list. A new entry below 18 that
is neither retired nor on the kept list is an entry nobody judged, and
the test goes red on it.
## Enumeration (done before any edit)
Re-derived from `packages/spec/src/conversions/registry.ts` on
`origin/main` `1418de4c7`, by importing `ALL_CONVERSIONS` and keeping
every entry whose `toMajor` is below 18 and that carries no
`retiredFromLoadPath`: 15 entries (3 at `toMajor: 11`, 2 at 15, 10 at
17). This matches the card's table exactly (10 + 5). There is no
difference to report.
## Premise check, per entry (before deciding)
For each entry, `fixture.before` (completed with the labels, flow `type`
and `edges` the fixtures leave out) went through `defineStack` twice:
once with the entry live, and once with `retiredFromLoadPath` set on it
in memory. The issues the second run adds are the answer an author would
meet after retirement:
- The six flow-node aliases and `full`: the second run adds a refusal at
the old key, naming the canonical key. That is the precondition, met.
For `full`, the text was the generic enum message, so the retirement
adds the prescription.
- `http_request`, `kind: 'jsx'`, `visibleOn`, `visibility` and `mongo`:
the second run adds nothing, so the old spelling would be accepted
silently. Kept.
- wait and connector: the second run refuses only when the block is
missing, and a loose key beside a declared block passes. Kept.
- retry: `flow.errorHandling.retryDelayMs` is tombstoned, but the
`try_catch` `config.retry` spelling passes. Kept.
PM mechanism assumption ②, measured: stored flows still heal.
`AutomationEngine.canonicalizeFlow` replays with `includeRetired: true`,
and its only exclusion (`CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION`) is
`flow-decision-mode-inclusive-explicit`, which is not in this family.
`service-automation`'s `crud-config-aliases.test.ts` now pins both
halves: `normalizeStackInput` hands `{ object, filters }` on as written,
and `registerFlow` plus `execute` still query `crm_lead` with `{ id:
'L1' }`.
## A second producer: the metadata save door's flow fallback
(`packages/metadata-protocol`)
This is outside the claim's file surface, so the landing spot and the
reason are stated here. `saveMetaItem` canonicalizes a flow through the
automation engine (the full chain, retired entries included). When no
automation service is reachable, or the canonicalizer throws, it judges
a fallback verdict body built with
`applyConversionsToFlow(request.item)`, in the authoring posture. Under
the #21982 ruling, that verdict must not depend on whether the host runs
an automation service. Retiring these seven entries would have broken
that: 9 tests in `protocol.save-flow-canonicalization.test.ts` went red,
because the fallback now refused `filters` and `functionName` bodies
that the canonicalizer heals.
The fix replays the chain the canonicalizer replays: `includeRetired:
true`, minus the same default flip, listed beside it with its reason
(`FLOW_FALLBACK_VERDICT_NOT_REPLAYED`, following the per-door exclusion
pattern of the engine and the artifact door). With the fix, the fallback
reaches the canonicalizer's verdict for every retired flow shape: the
seven retired here, and those retired earlier that walk a flow,
`flow-inert-keys-removed` (`flow.active` / `flow.template` /
`nodes[].outputSchema` / `errorHandling.fallbackNodeId`) and
`flow-node-wait-timeout-keys-removed` (`waitEventConfig.timeoutMs` /
`onTimeout`). So this is a behaviour change on the fallback path. On a
host with no automation service, a body carrying one of those older keys
was refused 422 by its tombstone before this PR; it is now healed for
the verdict and stored as submitted. That is what the service-backed
path already did, and what #21982 asks for. A new pin beside the #21982
block (`flow.active` on a no-service host saves, stored raw) holds it.
The 9 red tests are the before reading for the seven, and the after
reading is green.
## The record and strict-exit suites
`page-component-visibility-to-visibleWhen` stays live, so the three
suites that ride it (`stack-conversions-record.test.ts`,
`stack-conversion-record-door.test.ts`,
`validate-json-strict-exit.e2e.test.ts`) need no re-pointing. The wrong
sentence at `validate-json-strict-exit.e2e.test.ts:63-65` is fixed: the
conversion that retired is `page-header-subtitle-alias`.
## Pin sweep
The conversion ids and the old refusal wording were grepped across the
whole repo. These pins were flipped to the new semantics:
- Spec: `builtin-node-config.test.ts`, `io-node-config.test.ts`,
`schemaless-node-config.test.ts` and `sharing.test.ts`. Each now asserts
the prescription text (`` `X` was removed in @objectstack/spec 18 ``
plus `Use CANONICAL`). The `full` pin also asserts code `invalid_value`,
a single issue, and no "Expected one of" text.
- Spec: `flow-builtin-node-config-keys.test.ts`. `defineStack` now
refuses `STACK_SCHEMA_INVALID` / 422 at
`flows.0.nodes.1.config.filters`, and a stored body converted with
`includeRetired` parses clean.
- Spec: `conversions.test.ts` and `region-walk.test.ts`. The rewrite
cases run the data-at-rest posture.
- `service-automation`: `crud-config-aliases.test.ts`, as above.
- Comments that became false: `flow.test.ts`, the D3 entry
`18.flow-script-subflow-config-undeclared-keys-refused`, and the
reference page `schemaless-node-config.mdx`, which `gen:docs`
regenerates from the docblock.
## Verification
Commands, exit codes and real readings are in the report comment on the
card. Summary:
- `@objectstack/spec`: `test` 678 files / 20728 tests passed;
`test:repo` 60 / 1029 passed; `typecheck` exit 0; `check:generated` all
14 artifacts up to date. The four ratchets show zero change, as expected
for value and key narrowing on existing defs.
- Consumers: `service-automation` 186 files / 2486 tests passed; `lint`
139 / 6726 passed; `metadata-protocol` and `plugin-sharing` are in the
report.
- Derived gate families: `dispatch-gates.mjs --commands`, 115 commands,
reconciled with `--ran`.
## Acceptance notes
- Several executor comments in `service-automation` (`crud-nodes.ts`,
`notify-node.ts`, `map-node.ts`, `subflow-node.ts`, `screen-nodes.ts`)
and `lint` (`validate-flow-node-writes.ts`,
`validate-readonly-flow-writes.ts`, `validate-expressions.ts`) still say
an alias "is rewritten at load". That is still true of the seam those
executors sit behind (flow rehydration), so they are left alone.
- The save door with an automation service heals a retired flow alias
through the engine instead of refusing it. That was the case before this
PR (and already holds for the earlier retired flow-node tombstones). The
authoring funnel is the jurisdiction of `retiredFromLoadPath`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 51b9990 commit 5fd2c76
21 files changed
Lines changed: 543 additions & 158 deletions
File tree
- .changeset
- content/docs/references/automation
- packages
- cli/src/commands
- metadata-protocol/src
- services/service-automation/src/builtin
- spec/src
- automation
- conversions
- migrations/entries/semantic
- security
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | | - | |
86 | | - | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
87 | 89 | | |
88 | 90 | | |
89 | 91 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
401 | 401 | | |
402 | 402 | | |
403 | 403 | | |
404 | | - | |
405 | | - | |
406 | | - | |
407 | | - | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
408 | 411 | | |
409 | 412 | | |
410 | 413 | | |
| |||
430 | 433 | | |
431 | 434 | | |
432 | 435 | | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
433 | 450 | | |
434 | 451 | | |
435 | 452 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3247 | 3247 | | |
3248 | 3248 | | |
3249 | 3249 | | |
| 3250 | + | |
| 3251 | + | |
| 3252 | + | |
| 3253 | + | |
| 3254 | + | |
| 3255 | + | |
| 3256 | + | |
| 3257 | + | |
| 3258 | + | |
| 3259 | + | |
| 3260 | + | |
| 3261 | + | |
| 3262 | + | |
| 3263 | + | |
| 3264 | + | |
| 3265 | + | |
3250 | 3266 | | |
3251 | 3267 | | |
3252 | 3268 | | |
| |||
22405 | 22421 | | |
22406 | 22422 | | |
22407 | 22423 | | |
22408 | | - | |
| 22424 | + | |
22409 | 22425 | | |
22410 | | - | |
22411 | | - | |
22412 | | - | |
| 22426 | + | |
| 22427 | + | |
| 22428 | + | |
| 22429 | + | |
| 22430 | + | |
| 22431 | + | |
22413 | 22432 | | |
22414 | 22433 | | |
22415 | 22434 | | |
| |||
22484 | 22503 | | |
22485 | 22504 | | |
22486 | 22505 | | |
22487 | | - | |
| 22506 | + | |
| 22507 | + | |
| 22508 | + | |
| 22509 | + | |
| 22510 | + | |
| 22511 | + | |
22488 | 22512 | | |
22489 | 22513 | | |
22490 | 22514 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
106 | 109 | | |
107 | | - | |
108 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
109 | 114 | | |
110 | 115 | | |
111 | 116 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
78 | | - | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
79 | 81 | | |
80 | 82 | | |
81 | 83 | | |
| |||
90 | 92 | | |
91 | 93 | | |
92 | 94 | | |
93 | | - | |
| 95 | + | |
| 96 | + | |
94 | 97 | | |
95 | 98 | | |
96 | 99 | | |
| |||
101 | 104 | | |
102 | 105 | | |
103 | 106 | | |
104 | | - | |
| 107 | + | |
105 | 108 | | |
106 | 109 | | |
107 | 110 | | |
| |||
412 | 415 | | |
413 | 416 | | |
414 | 417 | | |
415 | | - | |
| 418 | + | |
| 419 | + | |
416 | 420 | | |
417 | 421 | | |
418 | 422 | | |
| |||
0 commit comments