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
44 changes: 36 additions & 8 deletions tools/workflow-engine/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@
* Usage:
* bun tools/workflow-engine/cli.ts --list-actions
* bun tools/workflow-engine/cli.ts --list-states
* bun tools/workflow-engine/cli.ts --list-du-cluster
* bun tools/workflow-engine/cli.ts --dry-run [--state <id>]
* bun tools/workflow-engine/cli.ts --validate
*
* Modes:
* --list-actions Print SEED_ACTION_CATALOG as structured JSON
* --list-states Print SEED_STATES + per-state available action list
* --dry-run Validate catalog + simulate one tick at given state
* (default: initial) without executing any side effects
* --validate Run catalog + state Otto-5-mods invariants; exit
* non-zero on violation
* --list-actions Print SEED_ACTION_CATALOG as structured JSON
* --list-states Print SEED_STATES + per-state available action list
* --list-du-cluster Print today's DU cluster (B-0917 + B-0918 + B-0919
* + B-0920) as structured JSON with variants +
* composes-with + substrate-anchors
* --dry-run Validate catalog + simulate one tick at given state
* (default: initial) without executing any side effects
* --validate Run catalog + state Otto-5-mods invariants; exit
* non-zero on violation
*
* Exit codes:
* 0 — operation successful
Expand All @@ -40,8 +44,9 @@ import {
validateCatalog,
type Action,
} from "./types";
import { DU_CLUSTER_CATALOG, computeDuClusterStats } from "./du-cluster";

type Mode = "list-actions" | "list-states" | "dry-run" | "validate";
type Mode = "list-actions" | "list-states" | "list-du-cluster" | "dry-run" | "validate";

interface ParsedArgs {
readonly mode: Mode;
Expand All @@ -53,11 +58,12 @@ function parseArgs(argv: ReadonlyArray<string>): ParsedArgs | { error: string }
if (args.length === 0) {
return {
error:
"no mode specified — use --list-actions, --list-states, --dry-run, or --validate",
"no mode specified — use --list-actions, --list-states, --list-du-cluster, --dry-run, or --validate",
};
}
if (args.includes("--list-actions")) return { mode: "list-actions" };
if (args.includes("--list-states")) return { mode: "list-states" };
if (args.includes("--list-du-cluster")) return { mode: "list-du-cluster" };
if (args.includes("--validate")) return { mode: "validate" };
if (args.includes("--dry-run")) {
const stateIdx = args.indexOf("--state");
Expand Down Expand Up @@ -107,6 +113,26 @@ function modeListStates(): number {
return 0;
}

function modeListDuCluster(): number {
const stats = computeDuClusterStats();
emitJson({
rowId: "B-0867",
subRow: "B-0867.5",
duClusterDate: "2026-05-28",
entryCount: stats.entryCount,
totalVariantCount: stats.totalVariantCount,
entries: DU_CLUSTER_CATALOG.map((e) => ({
id: e.id,
name: e.name,
variantCount: e.variantCount,
variants: e.variants,
composesWith: e.composesWith,
substrateAnchor: e.substrateAnchor,
})),
});
return 0;
}

function modeValidate(): number {
try {
validateCatalog(SEED_ACTION_CATALOG, SEED_STATES);
Expand Down Expand Up @@ -204,6 +230,8 @@ function main(argv: ReadonlyArray<string>): number {
return modeListActions();
case "list-states":
return modeListStates();
case "list-du-cluster":
return modeListDuCluster();
case "validate":
return modeValidate();
case "dry-run":
Expand Down
190 changes: 190 additions & 0 deletions tools/workflow-engine/du-cluster.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* tools/workflow-engine/du-cluster.test.ts
*
* Tests for today's DU cluster TS substrate:
* - IntrCtx (B-0917) — 5 context-types
* - WalletLifetime (B-0918) — 9 variants
* - MemoryBinding (B-0919) — 4 variants
* - MemoryLifetime (B-0920) — 5 variants
* - DuClusterCatalog aggregator
*/

import { describe, expect, test } from "bun:test";
import {
DU_CLUSTER_CATALOG,
INTR_CTX_KINDS,
MEMORY_BINDING_KINDS,
MEMORY_LIFETIME_KINDS,
WALLET_LIFETIME_KINDS,
computeDuClusterStats,
type IntrCtx,
type MemoryBinding,
type MemoryLifetime,
type WalletLifetime,
} from "./du-cluster";

describe("IntrCtx (B-0917)", () => {
test("5 named context-types", () => {
expect(INTR_CTX_KINDS).toHaveLength(5);
expect(INTR_CTX_KINDS).toContain("memetic");
expect(INTR_CTX_KINDS).toContain("prompt");
expect(INTR_CTX_KINDS).toContain("trust");
expect(INTR_CTX_KINDS).toContain("log");
expect(INTR_CTX_KINDS).toContain("otel");
});

test("IntrCtx shape has all 5 fields", () => {
const ctx: IntrCtx = {
memetic: "tonal-context-placeholder",
prompt: "operator-direction-placeholder",
trust: "trust-calculus-placeholder",
log: "audit-trail-placeholder",
otel: "activity-context-placeholder",
};
expect(Object.keys(ctx)).toHaveLength(5);
});
});

describe("WalletLifetime (B-0918)", () => {
test("9 variants per B-0918 spec", () => {
expect(WALLET_LIFETIME_KINDS).toHaveLength(9);
});

test("exhaustive variant set", () => {
const variants: WalletLifetime[] = [
{ kind: "uninitialized" },
{ kind: "initialized", walletId: "w1", signingAuthority: "k1", initialBalance: 0 },
{ kind: "transaction-pending", walletId: "w1", transaction: "t1", auditTrail: "a1" },
{
kind: "balance-updated",
walletId: "w1",
balanceDelta: 100,
cause: "c1",
auditTrail: "a1",
},
{
kind: "signing-authority-rotated",
walletId: "w1",
oldAuthority: "k1",
newAuthority: "k2",
consent: "ce1",
auditTrail: "a1",
},
{
kind: "trust-context-updated",
walletId: "w1",
oldTrust: "t1",
newTrust: "t2",
consent: "ce1",
auditTrail: "a1",
},
{
kind: "counterparty-engaged",
walletId: "w1",
counterparty: "cp1",
engagementTerms: "et1",
auditTrail: "a1",
},
{
kind: "emergency-frozen",
walletId: "w1",
freezeReason: "r1",
authorizedBy: "ab1",
auditTrail: "a1",
},
{ kind: "archived-read-only", walletId: "w1", finalAuditTrail: "fa1" },
];
expect(variants).toHaveLength(9);
const kindSet = new Set(variants.map((v) => v.kind));
expect(kindSet.size).toBe(9);
});

test("WALLET_LIFETIME_KINDS contains all expected kinds", () => {
expect(WALLET_LIFETIME_KINDS).toContain("uninitialized");
expect(WALLET_LIFETIME_KINDS).toContain("archived-read-only");
});
});

describe("MemoryBinding (B-0919)", () => {
test("4 variants per B-0919 spec", () => {
expect(MEMORY_BINDING_KINDS).toHaveLength(4);
});

test("exhaustive variant set", () => {
const variants: MemoryBinding[] = [
{ kind: "personal-only", persona: "otto", taggedOn: "2026-05-28" },
{ kind: "hat-only", hat: "architect", taggedOn: "2026-05-28" },
{
kind: "dual-tagged",
persona: "otto",
hat: "architect",
taggedOn: "2026-05-28",
consent: "ce1",
},
{
kind: "inherited-from-persona",
fromPersona: "otto",
toHat: "architect",
originalMemoryId: "m1",
transferredOn: "2026-05-28",
},
];
expect(variants).toHaveLength(4);
});

test("MEMORY_BINDING_KINDS contains expected kinds", () => {
expect(MEMORY_BINDING_KINDS).toContain("personal-only");
expect(MEMORY_BINDING_KINDS).toContain("dual-tagged");
});
});

describe("MemoryLifetime (B-0920)", () => {
test("5 variants per B-0920 spec", () => {
expect(MEMORY_LIFETIME_KINDS).toHaveLength(5);
});

test("exhaustive variant set", () => {
const variants: MemoryLifetime[] = [
"drafted",
"active",
"superseded",
"archived",
"retracted",
];
expect(variants).toHaveLength(5);
const variantSet = new Set(variants);
expect(variantSet.size).toBe(5);
});
});

describe("DU_CLUSTER_CATALOG", () => {
test("4 entries (B-0917 + B-0918 + B-0919 + B-0920)", () => {
expect(DU_CLUSTER_CATALOG).toHaveLength(4);
const ids = DU_CLUSTER_CATALOG.map((e) => e.id);
expect(ids).toContain("B-0917");
expect(ids).toContain("B-0918");
expect(ids).toContain("B-0919");
expect(ids).toContain("B-0920");
});

test("each entry has substrate-anchor + composesWith", () => {
for (const entry of DU_CLUSTER_CATALOG) {
expect(entry.substrateAnchor.length).toBeGreaterThan(0);
expect(entry.composesWith.length).toBeGreaterThan(0);
expect(entry.variantCount).toBe(entry.variants.length);
}
});
});

describe("computeDuClusterStats", () => {
test("aggregates 4 entries", () => {
const stats = computeDuClusterStats();
expect(stats.entryCount).toBe(4);
expect(stats.entries).toHaveLength(4);
});

test("totalVariantCount = 5 + 9 + 4 + 5 = 23", () => {
const stats = computeDuClusterStats();
expect(stats.totalVariantCount).toBe(23);
});
});
Loading
Loading