Skip to content

Commit cb47c7d

Browse files
chenkasirerclaude
andcommitted
Adapt simulation code to the canonical blueprint types
After rebasing onto the schema-driven data model, two points of drift from the old hand-written types needed fixing: - BlueprintTask was renamed to Task (the canonical, schema-generated name), in blueprint-simulate.ts and blueprint-load.ts. - Blueprint.tasks is now optional (tasks?: Task[]), because the JSON schema does not mark tasks as required. deriveSimulationBlueprint / stripSimulationDerivation / normalizeBlueprint / fetchBlueprint always populate tasks, so their return types now say so (Blueprint & { tasks }); the derive path also guards `bp.tasks ?? []` on its input. The blueprint-simulate test fixture uses `satisfies Blueprint` to keep its concrete tasks type. Also harmonized blueprint-flow.ts to use the SYSTEM_*_TASK_TYPE constants (ADR-0003) instead of repeating 'system.start' / 'system.end' literals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b775b35 commit cb47c7d

4 files changed

Lines changed: 25 additions & 20 deletions

File tree

src/utils/__tests__/blueprint-simulate.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
stripSimulationDerivation,
1616
} from '../blueprint-simulate';
1717

18-
const BLUEPRINT: Blueprint = {
18+
const BLUEPRINT = {
1919
version: '1.0',
2020
id: 'my-blueprint',
2121
name: 'My Blueprint',
@@ -35,7 +35,7 @@ const BLUEPRINT: Blueprint = {
3535
{ id: 'sleep', type: 'system.sleep', depends_on: [{ id: 'plan' }] },
3636
{ id: 'end', type: 'system.end', depends_on: [{ id: 'sleep' }] },
3737
],
38-
};
38+
} satisfies Blueprint;
3939

4040
describe('deriveSimulationBlueprintId', () => {
4141
it('appends the __sim suffix', () => {
@@ -315,7 +315,7 @@ describe('stripSimulationDerivation', () => {
315315
});
316316

317317
it('survives repeated laps of Simulate and Edit', () => {
318-
let bp = BLUEPRINT;
318+
let bp: Blueprint = BLUEPRINT;
319319
for (let lap = 0; lap < 3; lap++) {
320320
bp = stripSimulationDerivation(deriveSimulationBlueprint(bp));
321321
}

src/utils/blueprint-flow.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import type {
77
BlueprintMeta,
88
Task,
99
} from '../types/blueprint-schema';
10+
import {
11+
SYSTEM_START_TASK_TYPE,
12+
SYSTEM_END_TASK_TYPE,
13+
} from '../types/blueprint-schema';
1014

1115
/**
1216
* Serialization between the canonical Blueprint data model and the React Flow
@@ -41,7 +45,7 @@ export function blueprintToFlow(bp: Blueprint): { nodes: Node[]; edges: Edge[] }
4145
} satisfies AuthorNodeData,
4246
sourcePosition: Position.Right,
4347
targetPosition: Position.Left,
44-
deletable: task.type !== 'system.start' && task.type !== 'system.end',
48+
deletable: task.type !== SYSTEM_START_TASK_TYPE && task.type !== SYSTEM_END_TASK_TYPE,
4549
}));
4650

4751
const edges: Edge[] = [];

src/utils/blueprint-load.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {
22
Blueprint,
3-
BlueprintTask,
3+
Task,
44
Dependency,
55
TaskInput,
66
TaskOutput,
@@ -76,8 +76,8 @@ function toDependency(raw: Record<string, unknown>): Dependency {
7676
return withoutNulls({ id: String(raw.id ?? ''), type: raw.type }) as Dependency;
7777
}
7878

79-
function toTask(raw: Record<string, unknown>): BlueprintTask {
80-
const task: BlueprintTask = { id: String(raw.id ?? ''), type: String(raw.type ?? '') };
79+
function toTask(raw: Record<string, unknown>): Task {
80+
const task: Task = { id: String(raw.id ?? ''), type: String(raw.type ?? '') };
8181

8282
const description = asString(raw.description);
8383
const condition = asString(raw.condition);
@@ -100,14 +100,14 @@ function toTask(raw: Record<string, unknown>): BlueprintTask {
100100
}
101101

102102
/** Normalises a stored blueprint — COMPAS-wrapped or already flat — into an authoring `Blueprint`. */
103-
export function normalizeBlueprint(raw: unknown): Blueprint {
103+
export function normalizeBlueprint(raw: unknown): Blueprint & { tasks: Task[] } {
104104
const data = unwrap(raw);
105105
const id = asString(data.id);
106106
if (!id) {
107107
throw new Error('Blueprint is missing an id');
108108
}
109109

110-
const blueprint: Blueprint = {
110+
const blueprint: Blueprint & { tasks: Task[] } = {
111111
version: asString(data.version) ?? '1.0',
112112
id,
113113
name: asString(data.name) ?? id,
@@ -121,7 +121,7 @@ export function normalizeBlueprint(raw: unknown): Blueprint {
121121
}
122122

123123
/** Fetches a stored blueprint by id, ready to open in the authoring tool. */
124-
export async function fetchBlueprint(apiBaseUrl: string, id: string): Promise<Blueprint> {
124+
export async function fetchBlueprint(apiBaseUrl: string, id: string): Promise<Blueprint & { tasks: Task[] }> {
125125
const response = await fetch(`${apiBaseUrl}/blueprints/${encodeURIComponent(id)}`);
126126
if (!response.ok) {
127127
throw new Error(

src/utils/blueprint-simulate.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Blueprint, BlueprintTask, TaskOutput, TaskParam } from '../types/blueprint-schema';
1+
import type { Blueprint, Task, TaskOutput, TaskParam } from '../types/blueprint-schema';
22
import {
33
isSystemTaskType,
44
SYSTEM_COMPOSITE_TASK_TYPE,
@@ -31,14 +31,14 @@ export const SIMULATION_OPT_OUT_PARAM_NAME = '__sim_use_real_agent__';
3131

3232
/** True if `params` carries the opt-out flag. Operates on a bare param list so both the
3333
* derive-time rewrite and the authoring-tool panel (which only has `AuthorNodeData.params`,
34-
* not a full `BlueprintTask`) can share one check. */
34+
* not a full `Task`) can share one check. */
3535
export function isOptedOutParams(params: TaskParam[] | undefined): boolean {
3636
return (params ?? []).some(
3737
(p) => p.name === SIMULATION_OPT_OUT_PARAM_NAME && p.value === true,
3838
);
3939
}
4040

41-
export function isOptedOutOfSimulation(task: BlueprintTask): boolean {
41+
export function isOptedOutOfSimulation(task: Task): boolean {
4242
return isOptedOutParams(task.params);
4343
}
4444

@@ -90,7 +90,7 @@ export class CompositeTaskNotSupportedError extends Error {
9090
}
9191
}
9292

93-
function rewriteTaskType(task: BlueprintTask): string {
93+
function rewriteTaskType(task: Task): string {
9494
const { type } = task;
9595
if (isSystemTaskType(type)) {
9696
return type;
@@ -121,15 +121,16 @@ function simulatedOutputParams(outputs: TaskOutput[] | undefined): TaskParam[] {
121121
* Throws CompositeTaskNotSupportedError, naming the offending tasks, if the
122122
* blueprint contains any `system.composite` task.
123123
*/
124-
export function deriveSimulationBlueprint(bp: Blueprint): Blueprint {
125-
const compositeTaskIds = bp.tasks
124+
export function deriveSimulationBlueprint(bp: Blueprint): Blueprint & { tasks: Task[] } {
125+
const sourceTasks = bp.tasks ?? [];
126+
const compositeTaskIds = sourceTasks
126127
.filter((task) => task.type === SYSTEM_COMPOSITE_TASK_TYPE)
127128
.map((task) => task.id);
128129
if (compositeTaskIds.length) {
129130
throw new CompositeTaskNotSupportedError(compositeTaskIds);
130131
}
131132

132-
const tasks: BlueprintTask[] = bp.tasks.map((task) => {
133+
const tasks: Task[] = sourceTasks.map((task) => {
133134
const type = rewriteTaskType(task);
134135
// Only tasks actually rewritten to simulation.* need their outputs carried as params —
135136
// this also keeps the rewrite idempotent (re-deriving an already-derived task, whose type
@@ -166,14 +167,14 @@ export function deriveSimulationBlueprint(bp: Blueprint): Blueprint {
166167
* `__sim_out__` params from. Safe on a blueprint that was never derived — nothing matches, and
167168
* it passes through unchanged.
168169
*/
169-
export function stripSimulationDerivation(bp: Blueprint): Blueprint {
170-
const tasks: BlueprintTask[] = bp.tasks.map((task) => {
170+
export function stripSimulationDerivation(bp: Blueprint): Blueprint & { tasks: Task[] } {
171+
const tasks: Task[] = (bp.tasks ?? []).map((task) => {
171172
const type = stripSimulationTypePrefix(task.type);
172173
const params = (task.params ?? []).filter(
173174
(param) => !param.name.startsWith(SIMULATED_OUTPUT_PARAM_PREFIX),
174175
);
175176

176-
const stripped: BlueprintTask = { ...task, type };
177+
const stripped: Task = { ...task, type };
177178
if (params.length) {
178179
stripped.params = params;
179180
} else {

0 commit comments

Comments
 (0)