Repository navigation
Commit cb4c31d
Fixes #20862
Clause-②: no (narrowing)
## What this does
`POST /api/v1/automation` and `PUT /api/v1/automation/NAME` registered a
flow in the automation engine and wrote no metadata row. The next boot
binds flows from the stored metadata, so a created flow was gone after a
restart, and an update to a flow stored through `/meta` lost to the
stored definition. Both doors now save the flow as a tenant row through
the metadata protocol's own `saveMetaItem`, env-wide, which is the path
PR #20853 gave the clone door. This follows the triage direction on the
card (`5912757695`) and the maintainer's rule 2 recorded on #20761
(`5904938166`): every flow written through an authoring door goes
through one shared function.
- **One path for three doors.** `registerAndSaveFlow` in
`packages/runtime/src/domains/automation.ts` registers the definition in
the engine, then saves the same definition through `saveMetaItem({ type:
'flow', name, item })`. It passes no organization, no package and no
mode, so the row is live (`active`), like the engine registration. The
create door, the update door and the clone door all call it. There is no
second persistence path, and `saveMetaItem` is called, not changed.
- **Engine first, store second.** The engine's registration is still the
first check. Its refusal is answered as before (`400
VALIDATION_FAILED`), and nothing is saved.
- **A failed save leaves no registration of its own.** The store's
refusal is relayed with its own code and status. A new name is withdrawn
from the engine. A name the engine already held gets back the definition
it held, so a refused update does not take the flow down.
- **The refusals in front stay in front.** The #20679 locked-base
refusal on a packaged flow's name (`403 NOT_OVERRIDABLE`) and #20853's
`tenantAuthoredWriteRefusal` (`422 INVALID_METADATA`) still answer
before the engine or the store is touched.
- **No store, no save.** A composition with no metadata protocol keeps
the engine-only registration it always had, as the clone door already
did. The runtime pin states this case.
- **Draft or live:** the save uses `saveMetaItem`'s default mode, which
is `publish`, so the row is `active`. That matches the clone door. A
draft row would leave the engine running a flow the next boot does not
bind.
### The removal door keeps pace (beyond the card's two arms, declared)
Once the create door saves, `DELETE /automation/NAME`, which only
unregistered the flow in the engine, would leave the row behind, and the
next boot would bind the flow again. This was measured with the create
and update half alone on `1921e7e3e2`: a flow created then deleted
through the door still read `200` on `GET /meta/flow/NAME`, and `200` on
`GET /automation/NAME` after a cold boot. So `unregisterAndDeleteFlow`
removes the flow from the engine, then deletes the row through the
protocol's own `deleteMetaItem`, env-wide.
- The engine's removal refusal (`DELETE_RESTRICTED` / `409`) is still
raised before the store is touched.
- A name with no row is removed as before.
- A delete the store refuses puts the definition back in the engine and
relays the refusal.
## Repro, before this change
Measured with the booted pin
`packages/qa/dogfood/test/automation-authoring-doors-durable.dogfood.test.ts`
on base `2d5fe76f43` plus the pin commit `d09cee8a9c`, through
`bootStack` on the showcase with a database file:
- `POST /automation` answered `200`. `GET /meta/flow/NAME` answered `404
RESOURCE_NOT_FOUND`. After a cold boot on the same file, `GET
/automation/NAME` answered `404`.
- `PUT /automation/NAME` on a flow stored through `/meta` answered
`200`. `/meta` still served `Stored through /meta`. After the cold boot,
`GET /automation/NAME` served `Stored through /meta`, not the update.
## Declared narrowing
`Clause-②: no (narrowing)`. The changeset ships `minor` with a BREAKING
banner and an ADR-0087 disposition (`not-required
(no-migration-prescription)`). Nothing widens. The doors now refuse what
the metadata store refuses. Two bodies measured on the showcase answered
`200` before and are refused now:
- **A flow name with a leading underscore.** `FlowSchema.name` admits it
and the metadata item-name grammar does not. The door now answers `400
INVALID_REQUEST`.
- **A definition the runtime publish gate refuses.** The measured case
is a default edge that carries a condition
(`flow-default-edge-with-condition`). The door now answers `422
INVALID_METADATA`.
Neither flow could ever be stored, so before this change it ran only
until the next restart. The claim's reading was `no`. The measured diff
moves the doors' accept set for these bodies, so the declaration names
the arm.
## Pins
-
`packages/runtime/src/domains/automation-authoring-doors-durable.test.ts`
(new, 11 cases). It uses a real protocol over a real `SchemaRegistry`,
with the store's save and delete spied. It pins:
- the save arguments, and that the engine is called first;
- a failed create is withdrawn;
- a failed update puts back the definition the engine held;
- an engine refusal saves nothing;
- the locked-base and provenance refusals reach neither the engine nor
the store;
- the removal door's delete, its undo, and the §7.3 refusal coming
first;
- a composition with no protocol.
-
`packages/qa/dogfood/test/automation-authoring-doors-durable.dogfood.test.ts`
(new, 7 cases, cold boot on one database file):
- a created flow survives;
- an update to a `/meta`-stored flow survives;
- a create the store refuses answers `400 INVALID_REQUEST` and `GET`
answers `404`;
- an update the store refuses answers `422 INVALID_METADATA` and the
engine keeps the previous definition;
- a flow created then deleted stays deleted;
- control: a packaged name is still refused `403 NOT_OVERRIDABLE`, on
`PUT` and on a create onto it.
- `packages/runtime/src/domains/automation-packaged-base-lock.test.ts`:
the harness gains `standInStore()`. Four success-path cases now reach
the real save, and this harness's engine has no store (measured: 4 cases
at `500`, `this.engine.find is not a function`). They stand the store
in, the way the file's own clone case already did. No assertion changed.
## Verification, at head `43cf17d79b`
- `pnpm --filter @objectstack/runtime test` (local project): 296 files,
4245 passed, 1 skipped, `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/runtime typecheck` (tsc plus the test
layer) and `pnpm --filter @objectstack/dogfood typecheck`: exit 0.
- Dogfood: the new pin plus `flow-provenance-server-held`,
`packaged-flow-write-door-parity`, `automation-flow-clone-door` and
`automation-toggle-tenant-scope`: 5 files, 38 tests, exit 0. The runtime
`dist/` was rebuilt at this head first.
- `node scripts/pm/dispatch-gates.mjs --commands`: all 65 derived
commands were run, each exit recorded, and `--ran` answered `65 run, 0
NOT-MEASURED, 0 UNRUN`.
- `pnpm lint` (`eslint . --no-inline-config`, whole tree): exit 0.
- Also run: `pnpm check:durability-log-level` and `pnpm
check:adr-anchors`, exit 0 (on `a783880517`).
- **Reverse verification.** These were one-shot ablations through
`scripts/ablation-replace.mjs` in wrap mode, against the committed
implementation. The runtime pin imports source, so there is no `dist`
leg. Each restore was proven: blob equal to HEAD `2df430b334b6`, and
`git diff HEAD` empty.
- Save call removed: 4 of 11 red.
- Delete call removed: 2 of 11 red.
- Held-definition restore disabled: 1 of 11 red.
- The booted pin's before-leg is the repro above: 5 of 7 red on the
unfixed tree.
## Acceptance notes (observations, not filed)
- **A tenant-package-bound row.** The update door saves env-wide with no
package, as the triage direction says. On a flow stored through `/meta`
with `?package=`, the update therefore lands as a package-less row
beside the bound one. A bare `PUT /meta/flow/NAME` already does the same
today. Which row a later boot binds was NOT MEASURED. Taker: none.
- **Item-name grammar and `FlowSchema.name` disagree** on a leading
underscore. The refusal is loud and prescriptive at every runtime door,
and a code-shipped flow is loaded, not saved, so this is not a silent
trap. Taker: none.
- **Drafts.** `DELETE` removes the live row only, as `/meta`'s delete
without a state does. A pending draft row stays. Taker: none.
- **Version history.** Putting back a held definition re-registers it,
which adds one entry to the engine's in-memory version history. No
runtime door serves that history. Taker: none.
- **Stale tree.** `origin/main` moved after the merge `a783880517`. The
only gate-family input that changed is
`scripts/doc-authoring-prose-id.baseline.json`, which this diff does not
touch. CI measures the merge ref.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6f1f1c1 commit cb4c31d
5 files changed
Lines changed: 674 additions & 59 deletions
File tree
- .changeset
- packages
- qa/dogfood/test
- runtime/src/domains
| 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 | + | |
| 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 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 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 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
0 commit comments