Skip to content

Commit f2788e7

Browse files
os-zhuangclaude
andcommitted
test(showcase): retarget seed-vs-FSM guard to the #3433 exemption; regen exec-context ref doc
Two CI follow-ups to the #3433 seed exemption: - examples/app-showcase/test/seed.test.ts — the #3415 guard asserted every seeded status enters through `initialStates`, the exact constraint #3433 removes. Retargeted to the new contract: a seeded value must be a state the FSM declares (not a typo), and the fixture must seed ≥1 non-initial state so a regression back to a planned-only walk fails here. - content/docs/references/kernel/execution-context.mdx — regenerated so the spec reference doc reflects the new `seedReplay` field (generated-artifact drift gate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 25149d8 commit f2788e7

2 files changed

Lines changed: 46 additions & 33 deletions

File tree

content/docs/references/kernel/execution-context.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const result = ExecutionContext.parse(data);
6767
| **isSystem** | `boolean` || |
6868
| **skipTriggers** | `boolean` | optional | |
6969
| **skipAutomations** | `boolean` | optional | |
70+
| **seedReplay** | `boolean` | optional | |
7071
| **oauthScopes** | `string[]` | optional | |
7172
| **accessToken** | `string` | optional | |
7273
| **transaction** | `any` | optional | |

examples/app-showcase/test/seed.test.ts

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,20 @@ describe('showcase stack', () => {
3636
});
3737

3838
/**
39-
* Static shadow of what SeedLoader + validation do at boot (#3415): for every
40-
* object whose state_machine gates INSERT (`initialStates`), replay the seed
41-
* datasets in declaration order and assert each record enters through a legal
42-
* initial state and only moves along declared transitions. The fixture that
43-
* silently lost 4/5 projects (target status written directly on insert) can
44-
* never come back green.
39+
* Static shadow of the seed contract after #3433: a seed write is a curated
40+
* end-state fact, so the platform EXEMPTS it from the object's `state_machine`
41+
* rule — a project is seeded directly `active` / `on_hold` / `completed`
42+
* without walking the FSM up from `planned` (the three-phase walk workaround
43+
* of #3415 is gone). This guard pins the new contract for every object whose
44+
* state_machine gates INSERT (`initialStates`):
45+
* 1. every seeded value is still a state the FSM DECLARES (a curated fact,
46+
* not a typo — the exemption is not a license to write garbage); and
47+
* 2. the fixture actually EXERCISES the exemption by seeding ≥1 non-initial
48+
* state, so a regression back to "all rows enter as the initial state"
49+
* (a re-introduced walk, or a fixture that collapses the board to one
50+
* column) fails here. The #3433 failure was 1/5 projects surviving.
4551
*/
46-
describe('seed data vs state machines (#3415)', () => {
52+
describe('seed data vs state machines (#3433)', () => {
4753
const gated = (stack.objects ?? []).flatMap((o: any) =>
4854
(o.validations ?? [])
4955
.filter(
@@ -56,40 +62,46 @@ describe('seed data vs state machines (#3415)', () => {
5662
.map((v: any) => ({ object: o, rule: v })),
5763
);
5864

59-
it('covers the project status flow (the #3415 gate)', () => {
65+
it('covers the project status flow (the #3433 gate)', () => {
6066
expect(gated.map((g: any) => `${g.object.name}.${g.rule.field}`)).toContain('showcase_project.status');
6167
});
6268

6369
for (const { object, rule } of gated) {
64-
it(`${object.name}: seeded '${rule.field}' respects initialStates and transitions — including on replay`, () => {
70+
it(`${object.name}: seeded '${rule.field}' is FSM-exempt but stays within declared states (#3433)`, () => {
6571
const datasets = ShowcaseSeedData.filter((d: any) => d.object === object.name);
6672
expect(datasets.length).toBeGreaterThan(0);
67-
const current = new Map<string, string>();
68-
// Round 1 = fresh boot; round 2 = replay against the walked state.
69-
// Replay must also be violation-free (#3415 follow-up): `ignore`
70-
// datasets skip existing rows wholesale, and re-walked hops must be
71-
// legal transitions (which is what the reopen edge guarantees).
72-
for (const round of [1, 2]) {
73-
for (const ds of datasets as any[]) {
74-
for (const rec of ds.records as any[]) {
75-
const key = String(rec[ds.externalId ?? 'name']);
76-
const next = rec[rule.field];
77-
if (!current.has(key)) {
78-
// First appearance = INSERT. Seed inserts do NOT apply select
79-
// defaults, so a gated field must be explicit AND legal.
80-
expect(next, `'${key}' (round ${round}) must seed '${rule.field}' explicitly`).toBeDefined();
81-
expect(rule.initialStates, `'${key}' enters as '${next}'`).toContain(next);
82-
current.set(key, next);
83-
} else {
84-
if (ds.mode === 'ignore') continue; // existing rows untouched
85-
if (next === undefined || next === current.get(key)) continue; // no-op replay skips
86-
const from = current.get(key)!;
87-
expect(rule.transitions?.[from] ?? [], `'${key}' (round ${round}) ${from}${next}`).toContain(next);
88-
current.set(key, next);
89-
}
90-
}
73+
74+
// Every state the FSM knows about — the legal value universe, derived
75+
// from the rule itself (no dependency on the field's option shape).
76+
const fsmStates = new Set<string>(
77+
[
78+
...rule.initialStates,
79+
...Object.keys(rule.transitions ?? {}),
80+
...Object.values(rule.transitions ?? {}).flat(),
81+
].map(String),
82+
);
83+
84+
const seeded = new Set<string>();
85+
for (const ds of datasets as any[]) {
86+
for (const rec of ds.records as any[]) {
87+
const v = rec[rule.field];
88+
if (v === undefined || v === null) continue;
89+
const key = String(rec[ds.externalId ?? 'name']);
90+
// #3433: a seed value need NOT be an initialState (the FSM entry
91+
// guard is exempt), but it must be a state the machine declares.
92+
expect(fsmStates, `'${key}' seeds '${rule.field}=${String(v)}'`).toContain(String(v));
93+
seeded.add(String(v));
9194
}
9295
}
96+
97+
// The exemption must actually be used: seed at least one state the FSM
98+
// entry point would reject on INSERT. Guards against a silent regression
99+
// to a planned-only fixture (or a re-introduced FSM walk).
100+
const nonInitial = [...seeded].filter((v) => !rule.initialStates.includes(v));
101+
expect(
102+
nonInitial.length,
103+
`${object.name} seeds only initial states (${[...seeded].join(', ')}) — #3433 exemption unused`,
104+
).toBeGreaterThan(0);
93105
});
94106
}
95107
});

0 commit comments

Comments
 (0)