Commit 0283cb9
Fixes #15429
Clause-②: yes
Carries the maintainer's ruling `5793803317` (「跟主流对齐」, 2026-09-23) as
ONE change, routed to `domain:spec` by `5860007474` and dispatched by
the `domain:spec` seat 4 PM (`session_01CiCTczDo7tGhafXjf61dUJ`, claim
`5860277199`). Branch base `10ea9eb2e`; `origin/main` (`a78f731ad`)
merged through `os-regen-merge.sh`; every reading below is at head
`e92edee5b` unless it says otherwise; the patch commit `27a1a4598` (the
seat's answer A in `5861311629`) and the ruling C round (`5863827385`,
claim `5864486967`, head `18cbf4e2`) carry their own readings where
stated. _(Body revised by the seat at 2026-09-28T01:34Z from the
patch-round report `5861745226`.)_
Coordination card: **objectstack-ai/objectui#10750** (the designer
offers `mode`). Nothing is written in objectui here.
## What changes
1. **Exclusive by default (ruling item 1).**
`AutomationEngine.traverseNext`: on a `decision` node the conditioned
out-edges are evaluated in the order the flow's `edges` array declares
them and the FIRST one whose condition holds is the branch; its later
siblings are not evaluated and record the same `skipped` step a closed
gate does (`skippedBy` names the gate and the edge). `isDefault` is
unchanged: it runs when no conditioned sibling did. Scoped to
`decision`: conditioned out-edges of any other node type keep the
every-true-edge traversal (the census below found none).
2. **Explicit inclusive (item 2).** `config.mode: 'inclusive'` takes
every out-edge whose condition holds, one successor at a time (never
`Promise.all`; the pin's positive control proves the instrument can see
interleaving).
3. **Registration door (acceptance `5857171841`).** `registerFlow`
parses every decision's config through the spec's `DecisionConfigSchema`
and refuses the flow on any issue rooted at `mode` — the refinement
(`mode` beside a non-empty `conditions` list, either member) and the
value refusal — with the schema's own sentence at `node 'x' (decision)
at config.mode`, inside ADR-0031 regions too. Judged on `mode` alone,
deliberately: the same parse also refuses an undeclared key, but that
strictness binds at authoring by the standing decision in the module
header of `schemaless-node-config.zod.ts`, and refusing an inert extra
key at boot would be a second behaviour change riding a ruling that
ordered one. Measured reach of the alternative: zero decision nodes in
either corpus carry a key other than `conditions`, so promoting it later
is cheap.
4. **`os validate` door.** `@objectstack/lint` gains
`flow-decision-mode-invalid` (gating; the finding IS the schema's issue
message, so both doors say one sentence) and
`flow-decision-inclusive-overlap` (advisory, ruling item 4: `mode:
'inclusive'` with two or more conditioned out-edges; beside
`flow-decision-unconditional-branch`, which is about an out-edge nothing
gates).
5. **Migration (item 3).** ADR-0087 D2 conversion
`flow-decision-mode-inclusive-explicit` (protocol 18): a decision with
no `conditions` list, no `mode`, and two or more conditioned out-edges
(a `fault` edge is error routing; a blank condition is none; regions
walked through the shared slot table) gets `mode: 'inclusive'` written,
with a notice naming the count. One conditioned edge plus a default is
left alone; an authored `mode` is left alone (idempotent by
construction). No inference over the conditions, per the ruling. Paired
D3 entry `flow-decision-edge-branching-first-match` names the D2 id as a
whole word and carries the three-way judgment the diff asks for.
`MIGRATIONS_BY_MAJOR[18]` wires the id and its rationale grows a
sentence.
6. **Rewrites (item 5).**
`decision-overlapping-edge-conditions.pin.test.ts` is the contract pin
now, per its own header. `flows.mdx`, the `DecisionConfigSchema`
docblock and `mode` describe, the module header (the declared-ahead
sites of `5852576993` item 2), `logic-nodes.ts`, and `engine.ts`'s
traversal comment all describe both modes; the reference mdx is
regenerated.
## Ruling C (`5863827385`): stored rows take the new meaning — listed,
not rewritten
The ruling, verbatim:
> **Ruled: C.** Ruling `5793803317` item 3 (「保证已上线的流程行为不变」) is narrowed
to the surfaces that can carry it: authored sources (`os migrate meta
--from 17`) and built artifacts. Stored rows (`sys_metadata`) take the
new meaning on upgrade — a decision node with no `config.conditions`, no
`mode` and two or more conditioned out-edges evaluates first-match — and
the changeset and the upgrade guide state that as BREAKING, naming the
shape and the one-line fix (`mode: 'inclusive'`) for a node that meant
every branch. No stored-row rewrite, no cutoff, no read-path completion.
A (write-explicit on save plus read-path completion of a missing `mode`
to `inclusive`) and B (an operator-supplied cutoff) are not taken.
>
> Execution parameters (ruled in the same stroke):
> - PR #20344 (draft, `6bc84ba59`, CI green) lands after the at-tier
contract review (Clause-② yes), with these edits in its next round: the
D3 entry's and the changeset's 「judgment owed」 sentence replaced by this
ruling; a BREAKING paragraph naming the stored-row shape and the `mode:
'inclusive'` fix; `os migrate meta --stored` lists — report only, no
`--apply` effect — every stored decision node with two or more
conditioned out-edges and no `mode`, so an operator can review
candidates before and after the upgrade. ADR-0087 needs no addendum: the
artifact-door section's premise holds for sources and artifacts, and the
stored seam is stated in the changeset.
> - objectui#10750 (the designer writes `mode` explicitly on save)
proceeds on its own card; it is the same question's other end and is
wanted under this letter too.
> - Pins: a stored decision node lacking `mode` with overlapping
conditions evaluates first-match after upgrade; the `--stored` report
lists it and changes nothing; a source flow migrated with `--from 17`
carries explicit `mode: 'inclusive'` and still takes every branch.
What this round did: (a) prose — the D3 entry
`flow-decision-edge-branching-first-match` (reason tail and acceptance
sentence), the D2 docblock, step18's rationale and the changeset now
state BREAKING for a stored decision with no `config.conditions`, no
`mode` and two or more conditioned out-edges, the one-line fix `mode:
'inclusive'`, and the listing; the upgrade guide renders the D3 entry
(reason = Why not automatic, acceptanceCriteria = Done when) when
protocol 18 is cut; `DecisionConfigSchema.mode`'s describe/docblock and
the traversal comment say 'authored sources'; flows.mdx gains a
stored-flow callout and cli.mdx a --stored paragraph; (b) listing —
`StoredMigrationReport.decisionModeReview` (`StoredDecisionModeReview`:
row id, flow, org, package, state, node id, label, path) in
`@objectstack/metadata-protocol`, filled for every flow row on preview
and apply alike by `collectDecisionModeReview`, which runs the D2
entry's own `apply` over the stored body and discards the result (one
predicate; no engine needed; throws if the entry leaves the registry);
it moves no outcome, count, exit code or write;
`formatStoredMigrationReport` prints it with the fix, and the CLI and
`POST /meta/_migrate-stored` carry it unchanged, so `meta.ts` is not
edited; (c) the ruling's three pins, named below.
## PM mechanism assumptions, measured
**1. Where the traversal lives — held.** Both sites relocated by
content: the conditional loop under the old 「evaluate sequentially
(mutually exclusive)」 comment in `engine.ts` (`traverseNext`), and the
`config.conditions` first-match in `builtin/logic-nodes.ts` (untouched,
still label-narrowing). Declaration order is `flow.edges` array order:
`traverseNext` filters that array in place; `FlowSchema.parse` (region
transform included) and every conversion walker are copy-on-write maps
that never reorder; `normalizeStackInput` normalizes map-form
collections at the stack level and never touches a flow's `edges`;
`canonicalizeStoredFlow` runs those same two. Grep for any edge re-sort
across `packages/*/src` and `packages/*/*/src` (`edges.sort`,
`sortEdges`, `.edges.slice().sort`, `localeCompare` on edges): 0 hits.
NOT MEASURED: the Studio designer's own serialization order at save time
— objectui is not checked out in this container; server side,
`saveMetaItem` canonicalizes without reordering.
**2. The migration predicate — census.** Corpus: this repository's
examples at `10ea9eb2e` and `objectstack-ai/hotcrm` at `2f7b2326`
(read-only), every flow module loaded and every graph walked including
regions; platform packages ship no decision node (grep over
`packages/platform-objects`, `plugins`, `services`: only the executor
and the README).
| corpus | flows | decisions | rewritten (no list, ≥ 2 conditioned, no
`mode`) | left alone | non-decision nodes with a conditioned out-edge |
|:--|--:|--:|--:|--:|--:|
| examples (app-crm, app-todo, app-showcase) | 35 | 5 | 4 | 1
(`crm_convert_lead_wizard.check_converted`: 1 conditioned + `isDefault`)
| 0 |
| hotcrm | 13 | 25 | 13 (incl. `lead_conversion.decision_duplicate`, the
#1555 node, now three conditioned edges) | 12 (single-conditioned
guards, no default) | 0 |
Every one of the 17 positives is a hand-written partition by inspection
(a predicate beside its negation, `>` beside `<=`, `has()` beside
`!has()`, hotcrm's `CASE_HAS_OWNER` beside its exact complement,
`memberSource != "contacts"` beside `== "contacts"`), so first-match
changes none of their runs; the conversion still writes the key onto all
17, as ruled, and the D3 entry tells the author to delete it there. No
decision in either corpus carries a config key other than `conditions`.
`examples/**` is outside this claim's surface and is not edited: the
four in-tree positives partition, so nothing changes at boot.
**3. Stored flows — FAILED, and adapted.** The assumption was that the
conversion replays at rehydration like the other step-18 entries. It
cannot: this is a DEFAULT FLIP (the old shape still parses and now means
exclusive), and the flow rehydration seam serves post-flip authored
bodies too — `canonicalizeStoredFlow` is reached by the boot pull for
code-shipped flows, by `POST /automation`, by `saveMetaItem` (every
Studio save) and by `duplicatePackage`, all through one two-argument
signature, none dated. Replaying there would rewrite every NEW exclusive
decision into an inclusive one at registration and persist it at save,
and the ruled default would be unobservable. The registry's own doctrine
for this class (`excludeConversionIds`, the artifact door's
`DEFAULT_FLIPS_NOT_REPLAYED_HERE` for `app-hidden-to-unpublished` on
#17885, the WITHDRAWN `field-required-notnull-explicit` note) says a
seam that cannot state 「this body predates the flip」 refuses the entry
by id. So:
- the entry is `retiredFromLoadPath: true` (no authoring window —
「不留过渡窗口」) and replays where the operator asserts the source's age: `os
migrate meta --from 17` (the D3 chain), pinned both ways;
- `canonicalizeStoredFlow` refuses it by id
(`CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION`, reason at the call site),
pinned on `parsed`, `storable` and notices, with the chain as the firing
control;
- **Stored `sys_metadata` flows take the new meaning — ruled C
(`5863827385`).** A decision saved before this release with two or more
conditioned out-edges and no `mode` runs first-match after the upgrade;
no pass rewrites it (no stored-row migration, no cutoff, no read-path
completion). BREAKING, stated in the changeset and the D3 entry with the
one-line fix `mode: 'inclusive'`; `os migrate meta --stored` lists every
such node, report only.
- **The artifact-ingestion door refuses it too (patch commit
`27a1a4598`, the seat's answer A in `5861311629`).**
`packages/metadata-core/src/artifact-forward-conversion.ts` lists
`flow-decision-mode-inclusive-explicit` in
`DEFAULT_FLIPS_NOT_REPLAYED_HERE` beside the `app-hidden-to-unpublished`
precedent, with its reason: the door's trigger is the artifact's
declared `engines.protocol` floor, `^17.0.0` is what
`create-objectstack` stamps, so an app scaffolded today against the
exclusive contract lands inside the window and would otherwise be handed
an inclusive gateway it never asked for. The door pin has four legs
(subject, the strict parse the door feeds, negative, firing control),
and the engine seam's and the entry's docblocks now cite the door
precisely.
**4. Serial state — moved, merged.** `origin/main` gained #20286
(`view-overlay-owner-hidden-removed`) on the same registry lines;
`os-regen-merge.sh` merged it (both entries kept in landing order in
`conversions/registry.ts` and in `MIGRATIONS_BY_MAJOR[18]`, rationale
concatenated), `gen:migration-registry` regenerated to an identical
file, `check:generated` found every artifact current, and every sibling
symbol was asserted present on both sides by exact-name grep
(`viewOverlayOwnerHiddenRemoved` 3/3,
`view-overlay-owner-hidden-removed` 9/9, `view.zod.ts` `retiredKey`
21/21).
**5. Ruling C round (dispatch assumptions).** (1) The report type is
metadata-protocol's, not a spec contract type (`api/protocol.zod.ts`,
ruling 2C note), so the widening is `StoredMigrationReport` +
`StoredDecisionModeReview` there, covered by `Clause-②: yes`; the
renderer is also metadata-protocol's, so `meta.ts` needed no edit. (2)
The listing reads the stored body directly, with no engine, through the
D2 entry's `apply` by id; a flow row skipped for want of an engine still
lists. (3) `origin/main` `15bf186f5` (32 commits) merged through
`os-regen-merge.sh` as `df3f6a00`: two hand-written conflicts (both
registries; main's `form-layout-inline-grid-to-vertical`,
`currency-config-precision-removed`, `permission-rls-tags-removed` kept
ahead of ours), the reference mdx regenerated (`62771d8e`),
`gen:migration-registry` a no-op on the merged entries, sibling ids and
symbols asserted present 2/2 and 2/2. (4) #20316: branch
`claude/issue-20316-flow-node-config-build-doors` exists, no PR; overlap
with this PR is `conversions.test.ts` and `migrations/registry.ts` only;
nothing here touches `registerFlow`.
## Surface
22 files, +2157 / −205 (2362 changed lines against merge base
`15bf186f5`, under the 5000 human-merge threshold). Two files entered by
the claim's surface amendment (`5861311629`):
`packages/metadata-core/src/artifact-forward-conversion.ts` (only the
`DEFAULT_FLIPS_NOT_REPLAYED_HERE` array and its reason docblock) and
`packages/metadata-core/src/artifact-forward-conversion.test.ts` (the
door pin). Two files the claim did not spell are recorded there as
covered: `packages/spec/src/conversions/registry.ts` (where every D2
conversion lives) and `packages/lint/src/index.ts` (the two rule-id
exports, required by `rule-id-barrel-exports.test.ts`). ⛔ Not touched:
`flow-node-expression-paths.ts`, `examples/**`, `packages/cli/**`,
`packages/runtime/**`, `packages/rest/**`,
`packages/spec/src/contracts/**`, objectui. The ruling C round adds
`packages/metadata-protocol/src/{stored-migration,protocol,index}.ts`,
`protocol.stored-migration.test.ts` and
`content/docs/deployment/cli.mdx`. ⛔ Still not touched:
`packages/cli/**`, `packages/runtime/**`, `packages/rest/**`,
`packages/spec/src/contracts/**`, `examples/**`, `docs/adr/**`,
objectui.
## Pins that carry weight, and the ablations
`decision-overlapping-edge-conditions.pin.test.ts` (21 tests): two
overlapping true edges → exactly one runs, the first declared, the
sibling records `skipped`; declaration order decides (the same
predicates reversed take the other branch); `mode: 'inclusive'` → both
run nested, no skipped step; none true → `isDefault` runs in both modes;
a true edge beside a default passes the default over in both modes; a
`conditions` list still narrows by label; registration refuses the pair
(either member) and a bad value with the spec sentence, inside a loop
body too, and the flow is never armed; the four controls register; the
rehydration seam leaves the two-branch shape unrewritten while
`applyMetaMigrations(stack, 17, 18)` rewrites it; a non-decision node
keeps every-true-edge. `conversions.test.ts`: the fixture pair (2
notices) plus the predicate's edges, region reach, idempotence, the
authoring funnel's silence, and the seam refusal with its firing
control. `lint-flow-patterns.test.ts`: both rules, gating vs advisory,
controls, regions, no double report through rule (2).
`migrations.test.ts`'s census pin sees the D3 entry naming the D2 id.
`artifact-forward-conversion.test.ts` (`27a1a4598`): a `^17.0.0`-floor
artifact carrying a two-branch decision passes the door with no `mode`
written, no notice for the id and the same reference back; the strict
parse the door feeds receives no `mode`; `^99.0.0` shuts the window; and
the same fixture through `applyConversions` with `includeRetired: true`
and no refusal comes back `{ mode: inclusive }` with the entry's notice
(firing control). Ruling C (`5863827385`):
`decision-overlapping-edge-conditions.pin.test.ts` — a decision as a
pre-18 row stores it (JSON text, no `mode`, the hotcrm#1555 pair) passes
`canonicalizeStoredFlow` with no `mode` written, registers and runs
FIRST-MATCH (second branch `skipped` on its edge); the same body through
`applyMetaMigrations(stack, 17, 18)` carries `mode: inclusive` and runs
EVERY branch, nested, no skipped step (22 tests).
`protocol.stored-migration.test.ts` — the stored node is listed with
row, flow, node, label and path while the row stays canonical and clean;
`--apply` changes no byte and writes no history; a row rewritten for
another conversion persists no `mode`; no engine still lists; region
path; controls (either `mode`, one edge plus default, `conditions` list,
`fault` edge); the list equals the `--from 17` chain's write paths; the
renderer prints it beside the on-protocol verdict.
Both ablations ran from committed state through
`scripts/ablation-replace.mjs` (anchor hit 1→0, marker 0→1, blob hashes
printed), the reading was taken, and restore was `git checkout HEAD --
ABS_PATH` under a trap, proven by `git diff HEAD` clean and `git
hash-object` equal to the HEAD blob. No dist leg was owed: both suites
resolve their subject through `src` (`../engine.js` inside
service-automation; `./registry.js` inside spec).
- traversal: `if (exclusive && anyConditionMet)` → `if (false && …)`.
Blob `a60861d3985717a743cb32c16d9e3ba925dee3c7` →
`f0e25d39262ae22b38ef67b5affbba494c0023bf`. Ablated run: **6 failed**
(exactly the exclusivity, skipped-step, declaration-order,
written-exclusive, default-passed-over and seam-runs-exclusive pins), 15
passed (the controls, inclusive, default and registration pins).
Restored: `a60861d3…` on disk and at HEAD.
- predicate: `MIN_CONDITIONED_EDGES = 2` → `3`. Blob
`fd1a7902d480b791e7f53116eb38c97ad268fb78` →
`a03f5cbdf9f742aabf42e8400a1fc5df50b50d1b`. Ablated run: **5 failed**
(the fixture pair, the wiring pin, the two-edge rewrite, the left-alone
pin, the seam-refusal firing control), 216 passed. Restored: `fd1a7902…`
on disk and at HEAD.
- artifact door (`27a1a4598`): the id removed from
`DEFAULT_FLIPS_NOT_REPLAYED_HERE` (anchor 1→0, marker 0→1). Blob
`16742f49e72eaa98214eca097b9b14cef03e8809` →
`26220cf59c92d7b4daf75a74b17e5a076156503c`. Ablated run: **2 failed**
(the subject leg — `mode` written — and the strict-parse leg), 27 passed
(the firing control and the negative stayed green). Restored:
`16742f49…` on disk and at HEAD.
- stored listing (`18cbf4e2`): `protocol.ts` `for (const node of
collectDecisionModeReview(body)) {` emptied (`.slice(0, 0)`), blob
`711fded5ddea7d37b4f6d2a52f7b7a6a80db8081` →
`ce0c296d5134527c0634c1932ec88b59c6285dbc`; ablated run **5 failed** |
34 passed (the five listing pins; region, controls, parity and the
nothing-to-review control green); restored `711fded5…` on disk and at
HEAD.
- stored-row seam (`18cbf4e2`): `engine.ts` `excludeConversionIds:
CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION,` removed (a read-path
completion), blob `daac6de07304ae4051f1681ab4311c447a8ad3a9` →
`a3555237ccca29ff4ad888bd009cc97be4007ae8`; ablated run **7 failed** |
15 passed (the ruling C stored-row pin, both seam pins, four
exclusive-traversal pins; the `--from 17` source pin, inclusive,
default, registration and boundary green); restored `daac6de0…` on disk
and at HEAD.
## Tests, at `e92edee5b`, every exit captured after a redirect
- `pnpm --filter @objectstack/spec test` → exit 0: `Test Files 554
passed (554) · Tests 16366 passed | 1 todo`.
- `pnpm --filter @objectstack/service-automation exec vitest run
--maxWorkers=2` → exit 0: `Test Files 147 passed (147) · Tests 1782
passed (1782)`.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` →
exit 0: `Test Files 111 passed (111) · Tests 4313 passed (4313)`.
- `typecheck` for the same three packages → exit 0 each
(`check:test-typecheck` OK on each test layer).
- Importers of `DecisionConfigSchema` outside these packages:
`metadata-protocol`'s JSON-projection walk (a refinement projects
byte-identically) and `config-expression-ledger.test.ts` (in the
service-automation run above); no other importer of the traversal exists
(`registerFlow` callers in `runtime` and `plugin.ts` are unchanged call
sites).
- Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 114 commands on this branch; all 114
ran on `e92edee5b` with exit 0 (`check:dual-build-cjs-loads` and
`check:type-check-debt` first answered exit 3, PREREQUISITE NOT MET,
until the whole `packages/*` closure was built — 71/71 — then 0);
`--ran` reconciliation: `114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN`.
`check:generated` on the merged tree: every artifact current after
`gen:docs`. `spec-changes.json` and the upgrade guide render no
protocol-18 id yet (control: `report-joined-chart-removed` 0 hits too),
so their green is genuine, not a missed regeneration.
- Patch commit `27a1a4598`, readings at that head: `pnpm --filter
@objectstack/metadata-core exec vitest run --maxWorkers=2` → exit 0:
`Test Files 16 passed (16) · Tests 289 passed (289)`; metadata-core
`typecheck` → exit 0. Re-run because the diff reaches them (docblock
edits in `engine.ts` and `conversions/registry.ts`): service-automation
`Test Files 147 passed (147) · Tests 1782 passed (1782)`, spec `Test
Files 554 passed (554) · Tests 16366 passed | 1 todo`, both typechecks
exit 0; lint is not reached and was not re-run. Gates: the same 114
derived commands (0 added, 0 dropped), all exit 0 on `27a1a4598`;
`--ran`: `114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN`.
`check:type-check-debt` first refused (exit 3) because metadata-core's
cache-restored `dist/` was older than its source after the ablation's
restore rewrote the file; a direct `pnpm --filter
@objectstack/metadata-core build`, as the gate prescribes, and a re-run
gave 0.
- Ruling C round, at `18cbf4e2`: spec `test` `557 passed (557) · 16508
passed | 1 todo`; spec `test:repo` `35 passed (35) · 634 passed (634)`;
metadata-protocol `189 passed | 3 skipped (192) · 2745 passed | 19
skipped (2764)`; service-automation `147 passed (147) · 1783 passed
(1783)`; metadata-core `16 passed (16) · 289 passed (289)`; lint `113
passed (113) · 4713 passed (4713)`; cli unit `230 passed` plus the two
published-subpath pins `2 passed (2) · 29 passed (29)` after a
post-merge `pnpm install` (prerequisite, not a red); typecheck exit 0
for all six. Gates: 117 derived, 117 run, `--ran`: `117 derived, 117
run, 0 NOT-MEASURED, 0 UNRUN` (three first answered exit 3 PREREQUISITE
NOT MET until the full `./packages/*` closure and `client-react` /
`metadata-protocol` were built). Narrowed eslint over the PR's 18
changed `.ts` files: 0 errors, 0 warnings (no type-aware linting in
`eslint.config.mjs`). CI on `18cbf4e2`: 33 success, 2 skipped, all seven
required contexts success.
## Changeset grade, measured at landing
npm `latest` `@objectstack/spec` is `17.4.0` (`npm view`, re-measured
2026-09-28), whose published `DecisionConfig.json` declares `conditions`
only; `.changeset/19867-decision-config-mode.md` and
`.changeset/20168-…md` are still unconsumed, so `mode` is unreleased and
reaches its first release with the traversal that reads it and the
conversion that writes it. `Clause-②: yes` per the ruling's item 2 (the
D2/D3 entries and the two lint rules widen the published surface;
nothing published narrows). `minor` for `@objectstack/spec`,
`@objectstack/service-automation` and `@objectstack/lint`, with the
BREAKING banner, the FROM → TO block and the disposition marker
`registered flow-decision-mode-inclusive-explicit` (the changeset file
carries it in the gate's own form). `@objectstack/metadata-protocol`
`minor` (the report widens) and `@objectstack/metadata-core` `patch`
(the artifact door's refusal) join the bump list; the changeset carries
the ruling C BREAKING section.
## Acceptance notes
- **Landed on this PR (`27a1a4598`)**: the artifact door's refusal of
`flow-decision-mode-inclusive-explicit`, per the seat's answer A
(`5861311629`).
- **The stored-row half is ruled C (`5863827385`) and landed in this
round**: stored rows take the first-match meaning (BREAKING, stated with
the fix), `os migrate meta --stored` lists them report-only, and the
three pins hold it. objectui#10750 (the designer writes `mode` on save)
proceeds on its own card and is not this PR.
- `origin/main` moved two commits after this round's merge (`0d7ed5a3`
regenerates `packages/spec/src/migrations/registry.ts`); the landing lap
merges it through `os-regen-merge.sh`.
- `skills/objectstack-automation/SKILL.md` line 65 (「routed by edge
`condition` predicates」) stays true and does not mention `mode`;
governed surface, not touched.
- The REST door answers a registration refusal as `VALIDATION_ERROR` 400
through `flowDefinitionRefusal` (unchanged code path); not pinned here,
the runtime package is outside this surface.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 63e320a commit 0283cb9
22 files changed
Lines changed: 2157 additions & 205 deletions
File tree
- .changeset
- content/docs
- automation
- deployment
- references/automation
- packages
- lint/src
- metadata-core/src
- metadata-protocol/src
- services/service-automation/src
- builtin
- spec/src
- automation
- conversions
- migrations
- entries/semantic
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1370 | 1370 | | |
1371 | 1371 | | |
1372 | 1372 | | |
1373 | | - | |
| 1373 | + | |
1374 | 1374 | | |
1375 | 1375 | | |
1376 | 1376 | | |
| |||
1388 | 1388 | | |
1389 | 1389 | | |
1390 | 1390 | | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
1391 | 1446 | | |
1392 | 1447 | | |
1393 | 1448 | | |
| |||
1410 | 1465 | | |
1411 | 1466 | | |
1412 | 1467 | | |
1413 | | - | |
| 1468 | + | |
| 1469 | + | |
1414 | 1470 | | |
1415 | 1471 | | |
1416 | 1472 | | |
1417 | | - | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
1418 | 1476 | | |
1419 | 1477 | | |
1420 | 1478 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1203 | 1203 | | |
1204 | 1204 | | |
1205 | 1205 | | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
| 1212 | + | |
| 1213 | + | |
| 1214 | + | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
1206 | 1218 | | |
1207 | 1219 | | |
1208 | 1220 | | |
| |||
Lines changed: 17 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
76 | 81 | | |
77 | 82 | | |
78 | 83 | | |
| |||
96 | 101 | | |
97 | 102 | | |
98 | 103 | | |
99 | | - | |
100 | | - | |
101 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
102 | 108 | | |
103 | 109 | | |
104 | 110 | | |
| |||
137 | 143 | | |
138 | 144 | | |
139 | 145 | | |
140 | | - | |
| 146 | + | |
141 | 147 | | |
142 | 148 | | |
143 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
841 | 841 | | |
842 | 842 | | |
843 | 843 | | |
| 844 | + | |
| 845 | + | |
844 | 846 | | |
845 | 847 | | |
846 | 848 | | |
| |||
0 commit comments