Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 8 additions & 31 deletions .atomic/workflows/contract-child.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
import { defineWorkflow } from "@bastani/workflows";
import { defineWorkflow, Type } from "@bastani/workflows";
import type { WorkflowSerializableObject } from "@bastani/workflows";

export default defineWorkflow("contract-child")
.description("Child workflow for manual nesting validation. Returns declared serializable outputs for a parent workflow to consume.")
.input("topic", {
type: "text",
required: true,
description: "Topic the child workflow summarizes.",
})
.input("multiplier", {
type: "number",
default: 1,
description: "Finite number used to calculate child score.",
})
.output("result", {
type: "text",
required: true,
description: "Child summary string.",
})
.output("metadata", {
type: "object",
required: true,
description: "Structured child metadata.",
})
.output("checklist", {
type: "array",
required: true,
description: "Array output used by the parent nesting example.",
})
.output("score", {
type: "number",
required: true,
description: "Finite numeric child output.",
})
.input("topic", Type.String({ description: "Topic the child workflow summarizes." }))
.input("multiplier", Type.Number({ default: 1, description: "Finite number used to calculate child score." }))
.output("result", Type.String({ description: "Child summary string." }))
.output("metadata", Type.Unsafe<WorkflowSerializableObject>(Type.Object({}, { additionalProperties: true, description: "Structured child metadata." })))
.output("checklist", Type.Unsafe<readonly WorkflowSerializableObject[]>(Type.Array(Type.Unknown(), { description: "Array output used by the parent nesting example." })))
.output("score", Type.Number({ description: "Finite numeric child output." }))
.run(async (ctx) => {
const topic = ctx.inputs.topic;
const multiplier = Math.max(1, Math.min(5, Math.floor(ctx.inputs.multiplier)));
Expand Down
85 changes: 21 additions & 64 deletions .atomic/workflows/contract-complex-composed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { defineWorkflow } from "@bastani/workflows";
import type {
WorkflowSerializableObject,
WorkflowSerializableValue,
} from "@bastani/workflows";
import { defineWorkflow, Type } from "@bastani/workflows";
import type { WorkflowSerializableObject } from "@bastani/workflows";
import complexLeaf from "./contract-complex-leaf.js";

const VARIANTS = ["alpha", "beta", "gamma"] as const;
Expand Down Expand Up @@ -36,64 +33,22 @@ function clampPasses(value: number): number {
return Math.max(1, Math.min(3, Math.floor(value)));
}

function serializableObjectOrEmpty(
value: WorkflowSerializableValue | undefined,
): WorkflowSerializableObject {
if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
return {};
}

function stringOrFallback(value: WorkflowSerializableValue | undefined, fallback: string): string {
return typeof value === "string" ? value : fallback;
}

function numberOrZero(value: WorkflowSerializableValue | undefined): number {
return typeof value === "number" ? value : 0;
}

export default defineWorkflow("contract-complex-composed")
.description("Composed workflow for nested-import validation. Imports contract-complex-leaf and calls it multiple times.")
.input("topic", {
type: "text",
required: true,
description: "Topic forwarded into imported leaf workflows.",
})
.input("depth", {
type: "number",
default: 2,
description: "Tree depth forwarded into the leaf workflows. Clamped to 0..3.",
})
.input("variant", {
type: "select",
choices: VARIANTS,
default: "alpha",
description: "Variant forwarded into the leaf workflows.",
})
.input("passes", {
type: "number",
default: 2,
description: "Number of imported leaf workflow calls. Clamped to 1..3.",
})
.output("result", {
type: "text",
required: true,
description: "Composition summary string.",
})
.output("bundle", {
type: "object",
required: true,
description: "Deeply nested composition object built from child workflow outputs.",
})
.output("childDigests", {
type: "array",
required: true,
description: "Per-child digest array.",
})
.output("totalScore", {
type: "number",
required: true,
description: "Finite sum of child scores.",
})
.input("topic", Type.String({ description: "Topic forwarded into imported leaf workflows." }))
.input("depth", Type.Number({ default: 2, description: "Tree depth forwarded into the leaf workflows. Clamped to 0..3." }))
.input(
"variant",
Type.Union([Type.Literal("alpha"), Type.Literal("beta"), Type.Literal("gamma")], {
default: "alpha",
description: "Variant forwarded into the leaf workflows.",
}),
)
.input("passes", Type.Number({ default: 2, description: "Number of imported leaf workflow calls. Clamped to 1..3." }))
.output("result", Type.String({ description: "Composition summary string." }))
.output("bundle", Type.Unsafe<CompositionBundle>(Type.Object({}, { additionalProperties: true, description: "Deeply nested composition object built from child workflow outputs." })))
.output("childDigests", Type.Unsafe<readonly ChildDigest[]>(Type.Array(Type.Unknown(), { description: "Per-child digest array." })))
.output("totalScore", Type.Number({ description: "Finite sum of child scores." }))
.run(async (ctx) => {
const topic = ctx.inputs.topic;
const depth = clampDepth(ctx.inputs.depth);
Expand All @@ -111,13 +66,15 @@ export default defineWorkflow("contract-complex-composed")
},
});

// child.outputs is typed from contract-complex-leaf's declared output
// contract, so these are read directly without defensive narrowing.
childDigests.push({
pass,
workflow: child.workflow,
runId: child.runId,
result: stringOrFallback(child.outputs.result, "missing child result"),
score: numberOrZero(child.outputs.score),
packet: serializableObjectOrEmpty(child.outputs.packet),
result: child.outputs.result,
score: child.outputs.score,
packet: child.outputs.packet,
});
}

Expand Down
51 changes: 14 additions & 37 deletions .atomic/workflows/contract-complex-leaf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineWorkflow } from "@bastani/workflows";
import { defineWorkflow, Type } from "@bastani/workflows";
import type { WorkflowSerializableObject } from "@bastani/workflows";

const VARIANTS = ["alpha", "beta", "gamma"] as const;
Expand Down Expand Up @@ -106,42 +106,19 @@ function buildMatrix(size: number): readonly (readonly number[])[] {

export default defineWorkflow("contract-complex-leaf")
.description("Leaf workflow for complex nested-import validation. Returns a deeply nested JSON-serializable object graph.")
.input("topic", {
type: "text",
required: true,
description: "Topic used to generate the complex packet.",
})
.input("depth", {
type: "number",
default: 2,
description: "Tree depth for the complex packet. Clamped to 0..3.",
})
.input("variant", {
type: "select",
choices: VARIANTS,
default: "alpha",
description: "Variant used in nested records and tree nodes.",
})
.output("result", {
type: "text",
required: true,
description: "Leaf summary string.",
})
.output("packet", {
type: "object",
required: true,
description: "Deeply nested JSON-serializable packet.",
})
.output("records", {
type: "array",
required: true,
description: "Array of complex serializable records.",
})
.output("score", {
type: "number",
required: true,
description: "Finite numeric score derived from the complex packet.",
})
.input("topic", Type.String({ description: "Topic used to generate the complex packet." }))
.input("depth", Type.Number({ default: 2, description: "Tree depth for the complex packet. Clamped to 0..3." }))
.input(
"variant",
Type.Union([Type.Literal("alpha"), Type.Literal("beta"), Type.Literal("gamma")], {
default: "alpha",
description: "Variant used in nested records and tree nodes.",
}),
)
.output("result", Type.String({ description: "Leaf summary string." }))
.output("packet", Type.Unsafe<ComplexPacket>(Type.Object({}, { additionalProperties: true, description: "Deeply nested JSON-serializable packet." })))
.output("records", Type.Unsafe<readonly ComplexRecord[]>(Type.Array(Type.Unknown(), { description: "Array of complex serializable records." })))
.output("score", Type.Number({ description: "Finite numeric score derived from the complex packet." }))
.run(async (ctx) => {
const topic = ctx.inputs.topic;
const depth = clampDepth(ctx.inputs.depth);
Expand Down
131 changes: 67 additions & 64 deletions .atomic/workflows/contract-complex-root.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,69 @@
import { defineWorkflow } from "@bastani/workflows";
import type {
WorkflowSerializableObject,
WorkflowSerializableValue,
} from "@bastani/workflows";
import { defineWorkflow, Type } from "@bastani/workflows";
import type { WorkflowSerializableObject } from "@bastani/workflows";
import complexComposed from "./contract-complex-composed.js";

const VARIANTS = ["alpha", "beta", "gamma"] as const;

function objectOrEmpty(value: WorkflowSerializableValue | undefined): WorkflowSerializableObject {
if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
return {};
}

function arrayOrEmpty(value: WorkflowSerializableValue | undefined): readonly WorkflowSerializableValue[] {
return Array.isArray(value) ? value : [];
}

function numberOrZero(value: WorkflowSerializableValue | undefined): number {
return typeof value === "number" ? value : 0;
}

export default defineWorkflow("contract-complex-root")
.description("Root workflow for complex nested-import validation. Imports a composed workflow that itself imports and calls another workflow.")
.input("topic", {
type: "text",
required: true,
description: "Topic passed into the imported composed workflow.",
})
.input("depth", {
type: "number",
default: 2,
description: "Tree depth forwarded through the import chain.",
})
.input("variant", {
type: "select",
choices: VARIANTS,
default: "beta",
description: "Variant forwarded through the import chain.",
})
.input("passes", {
type: "number",
default: 2,
description: "Number of leaf calls made by the imported composed workflow.",
})
.output("result", {
type: "text",
required: true,
description: "Root summary string.",
})
.output("finalReport", {
type: "object",
required: true,
description: "Complex root-level report assembled from a nested imported composition.",
})
.output("importChain", {
type: "array",
required: true,
description: "Import/call chain represented as serializable objects.",
})
.output("totalScore", {
type: "number",
required: true,
description: "Finite score propagated from nested child outputs.",
})
.input("topic", Type.String({ description: "Topic passed into the imported composed workflow." }))
.input("depth", Type.Number({ default: 2, description: "Tree depth forwarded through the import chain." }))
.input(
"variant",
Type.Union([Type.Literal("alpha"), Type.Literal("beta"), Type.Literal("gamma")], {
default: "beta",
description: "Variant forwarded through the import chain.",
}),
)
.input("passes", Type.Number({ default: 2, description: "Number of leaf calls made by the imported composed workflow." }))
.output("result", Type.String({ description: "Root summary string." }))
.output(
"finalReport",
Type.Object(
{
topic: Type.String(),
rootInputs: Type.Object({
depth: Type.Number(),
variant: Type.Union([Type.Literal("alpha"), Type.Literal("beta"), Type.Literal("gamma")]),
passes: Type.Number(),
}),
composedRun: Type.Object({
workflow: Type.String(),
runId: Type.String(),
outputs: Type.Unsafe<WorkflowSerializableObject>(Type.Object({}, { additionalProperties: true })),
}),
bundle: Type.Unsafe<WorkflowSerializableObject>(Type.Object({}, { additionalProperties: true })),
childDigests: Type.Unsafe<readonly WorkflowSerializableObject[]>(Type.Array(Type.Unknown())),
checks: Type.Object({
composedWorkflowIsImported: Type.Boolean(),
composedWorkflowImportedLeafWorkflow: Type.Boolean(),
allValuesShouldBeJsonSerializable: Type.Boolean(),
}),
},
{ description: "Complex root-level report assembled from a nested imported composition." },
),
)
.output(
"importChain",
Type.Array(
Type.Union([
Type.Object({ level: Type.Number(), workflow: Type.String(), role: Type.String() }),
Type.Object({
level: Type.Number(),
workflow: Type.String(),
runId: Type.String(),
role: Type.String(),
declaredOutputKeys: Type.Array(Type.String()),
}),
Type.Object({
level: Type.Number(),
workflow: Type.String(),
role: Type.String(),
observedLeafCalls: Type.Number(),
}),
]),
{ description: "Import/call chain represented as serializable objects." },
),
)
.output("totalScore", Type.Number({ description: "Finite score propagated from nested child outputs." }))
.run(async (ctx) => {
const topic = ctx.inputs.topic;

Expand All @@ -84,9 +85,11 @@ export default defineWorkflow("contract-complex-root")
].join("\n"),
);

const totalScore = numberOrZero(composed.outputs.totalScore);
const bundle = objectOrEmpty(composed.outputs.bundle);
const childDigests = arrayOrEmpty(composed.outputs.childDigests);
// composed.outputs is typed from contract-complex-composed's declared
// output contract, so these are read directly without defensive narrowing.
const totalScore = composed.outputs.totalScore;
const bundle = composed.outputs.bundle;
const childDigests = composed.outputs.childDigests;
const importChain = [
{
level: 0,
Expand Down
Loading
Loading