Skip to content

Commit a4da73e

Browse files
committed
chore(changeset): @objectstack/spec minor — duplicate edge ids refused at parse, ADR-0087 no-migration-prescription
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
1 parent 8140397 commit a4da73e

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: `FlowSchema` refuses a flow whose `edges[]` declares the same id twice (#14964)
6+
7+
<!-- adr-0087: not-required (no-migration-prescription) No authorable key is renamed, retired or re-typed: `edges[].id` keeps its name, its type and its describe, and every flow whose edge ids are unique parses byte-identically. The only newly refused shape is two edges sharing one id — a collision, not a spelling — and its remedy is to renumber one of the two, which is authoring intent no `objectstack migrate meta` rewrite can choose for the author. The Zone-2 census over this repo (776 `edges[]` arrays, 1,098 edges under `packages/**` and `examples/**`, with a lit control) found zero instances, so there is no in-repo file to name. -->
8+
9+
**BREAKING** accept-set narrowing on `FlowSchema` — a flow whose `edges[]`
10+
carries two edges with the same `id` is now **refused at parse time** — by
11+
`FlowSchema.parse` / `safeParse`, `defineFlow`, and every door that validates a
12+
flow through the schema (`objectstack validate`, the runtime publish gate, a
13+
stack's `flows[]`) — where it used to parse on green. Shipped as `minor` under
14+
the repo's launch-window convention for breaking changes. Maintainer ruling
15+
2026-09-05 on #14964 (director decision batch #40, verbatim 「同意」): option
16+
A — an `error`, not a `warning`; no opt-out, no transition window.
17+
18+
Every reader of an edge id assumes the ids in a flow are unique — a designer,
19+
a BPMN export, a flow diff, any traversal that dedupes by id — and nothing
20+
enforced it. A real duplicate (`id: 'e20'` on two edges of one flow) shipped
21+
through two releases of green CI in a downstream app and was inert only
22+
because the engine keys out-edges by `source`, never by `id`: the collision is
23+
invisible until something keys on ids, and then silently wrong rather than
24+
loudly broken. The id space is hand-authored, so the next author picking a
25+
"free" id from the sequence had no way to know it was taken.
26+
27+
**What changes** (`packages/spec/src/automation/flow.zod.ts`): a `superRefine`
28+
on the flow's `edges[]`. Each later occurrence of an already-declared id raises
29+
one `custom` issue, anchored at `edges[N].id` of the *later* edge and naming
30+
both positions, so the formatted error points at the edge to renumber:
31+
32+
```text
33+
✗ edges.7.id: Duplicate edge id `e20` — `edges[7]` reuses the id already declared by `edges[3]`; every edge id in a flow must be unique. Renumber one of them: …
34+
```
35+
36+
**What does NOT change:** `edges[].id` keeps its name, type and describe; the
37+
node vocabulary, the edge `type` enum and every other refusal are untouched;
38+
a flow with unique edge ids (or no edges) parses exactly as before. Node ids
39+
are not covered by this change.
40+
41+
## FROM → TO
42+
43+
```ts
44+
// before — parsed on green, both edges keyed 'e20'
45+
edges: [
46+
{ id: 'e20', source: 'qualify', target: 'convert' },
47+
{ id: 'e20', source: 'convert', target: 'end' },
48+
]
49+
50+
// after — refused at parse (edges.1.id: Duplicate edge id `e20` …); renumber the later one:
51+
edges: [
52+
{ id: 'e20', source: 'qualify', target: 'convert' },
53+
{ id: 'e21', source: 'convert', target: 'end' },
54+
]
55+
```
56+
57+
**Remedy.** Renumber the later edge to an id no other edge in that flow
58+
carries; nothing else in the flow needs to move. The census over this
59+
repository found no flow to migrate, so this is a release note, not a
60+
migration: no shipped example, fixture or seed in `packages/**` or
61+
`examples/**` declares a duplicate edge id, and the pinned objectui tree
62+
carries none in its authored flows. The one known downstream instance was
63+
renumbered before this change (hotcrm PR #1571).

0 commit comments

Comments
 (0)