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
2 changes: 1 addition & 1 deletion apps/presentation/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"smoke:usage-progress": "rm -rf /tmp/loopx-usage-progress-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --resolveJsonModule --esModuleInterop --jsx react-jsx --skipLibCheck --strict --outDir /tmp/loopx-usage-progress-smoke smoke/usage-progress-smoke.ts src/features/personal-workspace/personal-workspace-model.ts && node /tmp/loopx-usage-progress-smoke/apps/presentation/dashboard/smoke/usage-progress-smoke.js",
"smoke:action-review-plan": "rm -rf .local/action-review-plan-smoke && tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --skipLibCheck --strict --rootDir ../../.. --outDir .local/action-review-plan-smoke smoke/action-review-plan-smoke.ts ../../../loopx/control_plane/presentation/action_review_plan.ts src/vite-env.d.ts && node .local/action-review-plan-smoke/apps/presentation/dashboard/smoke/action-review-plan-smoke.js",
"smoke:attention-details": "tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --resolveJsonModule --esModuleInterop --jsx react-jsx --skipLibCheck --strict --outDir /tmp/loopx-attention-details-smoke smoke/attention-details-smoke.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-attention-details-smoke/apps/presentation/dashboard/smoke/attention-details-smoke.js",
"smoke:delivery-review": "tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck --strict --rootDir src --outDir node_modules/.cache/delivery-review src/data/delivery-review.ts src/data/goal-acceptance-observation.ts src/features/personal-workspace/delivery-review-copy.ts && node smoke/delivery-review-smoke.mjs",
"smoke:delivery-review": "tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck --strict --rootDir src --outDir node_modules/.cache/delivery-review src/data/delivery-review.ts src/data/goal-work-map.ts src/data/goal-acceptance-observation.ts src/features/personal-workspace/delivery-review-copy.ts && node smoke/delivery-review-smoke.mjs",
"smoke:team-artifact-comparison": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --skipLibCheck --strict --rootDir src --outDir node_modules/.cache/team-comparison src/features/personal-workspace/team-artifact-comparison.ts src/vite-env.d.ts && node smoke/team-artifact-comparison-smoke.mjs",
"smoke:team-report": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --jsx react-jsx --skipLibCheck --strict --rootDir src --outDir node_modules/.cache/team-report src/features/personal-workspace/team-artifact-content.tsx src/vite-env.d.ts && node smoke/team-report-smoke.mjs",
"build:chat:vite": "tsc --noEmit && vite build --config vite.chat.config.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@ for (const copy of Object.values(deliveryReviewCopy)) {
assert.ok(markdown.includes(`${copy.guards}: ${copy.unavailable}`), "Unavailable decisions must not export as zero pending");
}
console.log("delivery review: identity, scope, relationships, partial coverage, filtering, export and negative contracts passed");
await import("./goal-work-map-smoke.mjs");
await import("./goal-acceptance-contract-smoke.mjs");
99 changes: 99 additions & 0 deletions apps/presentation/dashboard/smoke/goal-work-map-smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import assert from "node:assert/strict";
import { deliveryReviewMarkdown, parseDeliveryReview } from "../node_modules/.cache/delivery-review/data/delivery-review.js";
import { goalWorkMapCoverage, goalWorkMapLayout, goalWorkMapLineage, goalWorkMapSharedOwner, goalWorkMapSummary, goalWorkMapTone } from "../node_modules/.cache/delivery-review/data/goal-work-map.js";
import { deliveryReviewCopy } from "../node_modules/.cache/delivery-review/features/personal-workspace/delivery-review-copy.js";

const node = (id, kind, state, depth) => ({ node_id: id, kind, title: `Title ${id}`, state, depth, refs: { todo_ids: [`todo_${id}`] } });
const edge = (from, to, relation = "depends_on", enforcement = "typed_lifecycle") =>
({ edge_id: `${from}_${to}_${relation}`, from_node_id: from, to_node_id: to, relation, enforcement, reason: "Recorded relation" });
// A decision gates two blocked tasks; finished history feeds open work through
// two relations between one pair; a watch has no recorded link.
const map = {
schema_version: "goal_task_map_v0", mode: "read_only", goal_id: "map-demo",
limits: { node_limit: 120, emitted_node_count: 7, omitted_node_count: 0, source_truncated: false, missing_endpoint_count: 0, cycle_edge_count: 0, topology_complete: true },
nodes: [node("gate", "gate", "open", 0), node("reserve", "deliverable", "blocked", 1), node("deposit", "deliverable", "blocked", 2),
node("scope", "deliverable", "done", 0), node("venues", "deliverable", "done", 1), node("budget", "deliverable", "open", 2),
node("watch", "monitor", "open", 0)],
edges: [edge("reserve", "gate"), edge("deposit", "reserve"), edge("venues", "scope", "continues", "lineage_only"),
edge("budget", "venues", "continues", "lineage_only"), edge("budget", "venues", "depends_on", "typed_condition")],
};
const snapshot = { ok: true, goal_id: "map-demo", observed_at: "2026-09-01T00:00:00Z", graph: null, goal_map: map, acceptance: null };
const parsed = parseDeliveryReview(snapshot, "map-demo").goal_map;
const ids = nodes => nodes.map(item => item.node_id);
const grids = layout => layout.groups.map(group => group.columns.map(ids));

assert.deepEqual(goalWorkMapSummary(parsed), { work: 5, done: 2, blocked: 2, waiting: 0, decisions: 1, watches: 1 });
assert.equal(goalWorkMapTone(node("x", "gate", "done", 0)), "done", "A decided gate no longer asks for a decision");
assert.equal(goalWorkMapTone(node("x", "deliverable", "ready", 0)), "open");

const current = goalWorkMapLayout(parsed, "current");
assert.deepEqual(grids(current), [[["gate"], ["reserve"], ["deposit"]], [["venues"], ["budget"]]],
"Each chain gets its own grid, the one needing a decision first, with hidden depths compacted");
assert.equal(current.hiddenCount, 1, "Older history beyond direct prerequisites is hidden, not dropped");
assert.deepEqual(ids(current.unlinked), ["watch"]);
assert.equal(current.edges.length, 4, "Parallel relations between one pair survive layout");
const all = goalWorkMapLayout(parsed, "all");
assert.deepEqual(grids(all), [[["gate"], ["reserve"], ["deposit"]], [["scope"], ["venues"], ["budget"]]]);
assert.equal(all.hiddenCount, 0);
const gap = goalWorkMapLayout({ ...parsed, nodes: [node("a", "deliverable", "done", 0), node("b", "deliverable", "done", 1), node("c", "deliverable", "open", 2)],
edges: [edge("b", "a"), edge("c", "b")] }, "current");
assert.deepEqual(grids(gap), [[["b"], ["c"]]], "Hidden depths leave no empty columns");
const joined = goalWorkMapLayout({ ...parsed, nodes: [node("p", "deliverable", "open", 0), node("q", "deliverable", "open", 0), node("r", "deliverable", "open", 1)],
edges: [edge("r", "p"), edge("r", "q")] }, "all");
assert.deepEqual(grids(joined), [[["p", "q"], ["r"]]], "A shared dependent keeps its prerequisites in one chain");

const lineage = goalWorkMapLineage(parsed.edges, "reserve");
assert.deepEqual([...lineage.nodes].sort(), ["deposit", "gate", "reserve"], "Lineage follows only recorded links");
assert.equal(lineage.edges.size, 2);
assert.equal(goalWorkMapLineage(parsed.edges, "budget").edges.size, 3, "Transitive prerequisites are traced");
assert.equal(goalWorkMapLineage(parsed.edges, null).nodes.size, 0);

// Deferred work leaves the current view unless active work needs it or an open decision unblocks it.
const parked = goalWorkMapLayout({ ...parsed, nodes: [node("ask", "gate", "open", 0), node("later", "deliverable", "waiting", 1),
node("run", "deliverable", "open", 1), node("input", "deliverable", "waiting", 0), node("shelf", "deliverable", "waiting", 0)],
edges: [edge("later", "ask", "depends_on", "typed_condition"), edge("run", "input", "depends_on", "typed_condition")] }, "current");
assert.deepEqual(new Set(parked.groups.flatMap(group => group.columns.flat()).map(item => item.node_id)), new Set(["ask", "later", "run", "input"]));
assert.equal(parked.hiddenCount, 1, "Unrelated deferred work is hidden, not dropped");

// A long finished history behind one step collapses into a count instead of a column.
const history = (count) => goalWorkMapLayout({ ...parsed, nodes: [node("step", "deliverable", "open", 1), node("next", "deliverable", "open", 2),
...Array.from({ length: count }, (_, index) => node(`done${index}`, "deliverable", "done", 0))],
edges: [edge("next", "step", "depends_on", "typed_condition"), ...Array.from({ length: count }, (_, index) => edge("step", `done${index}`))] }, "current");
assert.deepEqual(grids(history(2)), [[["done0", "done1"], ["step"], ["next"]]], "Two finished prerequisites stay as context");
assert.deepEqual(grids(history(3)), [[["step"], ["next"]]]);
assert.equal(history(3).collapsed.get("step"), 3);
assert.equal(history(3).hiddenCount, 3, "Collapsed history is counted, not dropped");
assert.equal(goalWorkMapLayout({ ...parsed, nodes: history(3).groups[0].columns.flat() }, "all").collapsed.size, 0, "The full map collapses nothing");

const limits = parsed.limits;
assert.equal(goalWorkMapCoverage(parsed), "complete");
assert.equal(goalWorkMapCoverage({ ...parsed, limits: { ...limits, missing_endpoint_count: 2, topology_complete: false } }), "partial",
"Missing endpoints do not prove the target was archived or belongs to another Goal");
for (const change of [{ omitted_node_count: 1 }, { source_truncated: true }, { cycle_edge_count: 1 }]) {
assert.equal(goalWorkMapCoverage({ ...parsed, limits: { ...limits, missing_endpoint_count: 2, topology_complete: false, ...change } }), "partial");
}
assert.equal(goalWorkMapCoverage({ ...parsed, limits: { ...limits, topology_complete: false } }), "partial", "Unexplained incompleteness stays partial");
const owned = (...owners) => ({ ...parsed, nodes: owners.map((owner, index) => ({ ...node(`n${index}`, "deliverable", "open", 0), ...(owner ? { owner_agent: owner } : {}) })) });
assert.equal(goalWorkMapSharedOwner(owned("solo", "solo", null)), "solo");
assert.equal(goalWorkMapSharedOwner(owned("solo", "pair")), null);
assert.equal(goalWorkMapSharedOwner(owned("solo")), null, "A single owned item keeps its owner on the card");

for (const mutation of [
{ ...map, goal_id: "other-goal" },
{ ...map, nodes: [...map.nodes, map.nodes[0]] },
{ ...map, edges: [...map.edges, map.edges[0]] },
{ ...map, edges: [{ ...map.edges[0], to_node_id: "missing" }] },
{ ...map, edges: [{ ...map.edges[0], relation: "blocks" }] },
{ ...map, nodes: [{ ...map.nodes[0], kind: "evidence" }] },
{ ...map, mode: "writable" },
]) assert.throws(() => parseDeliveryReview({ ...snapshot, goal_map: mutation }, "map-demo"));

for (const copy of Object.values(deliveryReviewCopy)) {
const markdown = deliveryReviewMarkdown(parseDeliveryReview(snapshot, "map-demo"), copy);
assert.ok(markdown.includes(`## ${copy.workMap.title}`) && markdown.includes(copy.workMap.boundary));
assert.ok(markdown.includes('"topology_complete": true'), "Exports keep coverage limits next to the map");
assert.ok(markdown.includes(`Title budget → ${copy.workMap.relation.depends_on} → Title venues`)
&& markdown.includes(`Title budget → ${copy.workMap.relation.continues} → Title venues`));
assert.ok(!deliveryReviewMarkdown(parseDeliveryReview({ ...snapshot, goal_map: null }, "map-demo"), copy).includes(`## ${copy.workMap.title}`));
}
console.log("goal work map: summary, focus layout, lineage, coverage, export and negative contracts passed");
41 changes: 40 additions & 1 deletion apps/presentation/dashboard/src/data/delivery-review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,49 @@ const graphSchema = z.object({
context.addIssue({ code: "custom", message: "Graph identities or endpoints are invalid" });
}
});
const goalMapNodeSchema = z.object({
node_id: z.string().min(1), kind: z.enum(["deliverable", "gate", "monitor"]), title: z.string(),
state: nodeSchema.shape.state, depth: z.number().int().nonnegative(), refs: refsSchema,
owner_agent: z.string().optional(), task_domain: z.string().optional(),
});
const goalMapSchema = z.object({
schema_version: z.literal("goal_task_map_v0"), mode: z.literal("read_only"), goal_id: z.string(),
limits: z.object({
node_limit: z.number().int().positive(), emitted_node_count: z.number().int().nonnegative(),
omitted_node_count: z.number().int().nonnegative(), source_truncated: z.boolean(),
missing_endpoint_count: z.number().int().nonnegative(), cycle_edge_count: z.number().int().nonnegative(),
topology_complete: z.boolean(),
}),
nodes: z.array(goalMapNodeSchema),
edges: z.array(z.object({
edge_id: z.string().min(1), from_node_id: z.string(), to_node_id: z.string(),
relation: z.enum(["depends_on", "continues", "supersedes"]),
enforcement: z.enum(["typed_lifecycle", "typed_condition", "lineage_only"]), reason: z.string(),
})),
}).superRefine((map, context) => {
const nodes = new Set(map.nodes.map(node => node.node_id));
if (nodes.size !== map.nodes.length || new Set(map.edges.map(edge => edge.edge_id)).size !== map.edges.length
|| map.edges.some(edge => !nodes.has(edge.from_node_id) || !nodes.has(edge.to_node_id))) {
context.addIssue({ code: "custom", message: "Goal map identities or endpoints are invalid" });
}
});
const snapshotSchema = z.object({
ok: z.literal(true), goal_id: z.string(), observed_at: z.string().datetime({ offset: true }),
graph: graphSchema.nullable(), acceptance: goalAcceptanceObservationSchema.nullable(),
graph: graphSchema.nullable(), goal_map: goalMapSchema.nullable().optional(),
acceptance: goalAcceptanceObservationSchema.nullable(),
});
export type DeliveryReviewSnapshot = z.infer<typeof snapshotSchema>;
export type ReviewGraph = NonNullable<DeliveryReviewSnapshot["graph"]>;
export type ReviewNode = ReviewGraph["nodes"][number];
export type ReviewRelation = ReviewGraph["edges"][number]["relation"];
export type ReviewFocus = "all" | "conditions" | "evidence" | "related";
type GoalMapNode = z.infer<typeof goalMapNodeSchema>;
type GoalMapEdge = z.infer<typeof goalMapSchema>["edges"][number];

export function parseDeliveryReview(value: unknown, goalId: string): DeliveryReviewSnapshot {
const result = snapshotSchema.parse(value);
if (result.goal_id !== goalId || (result.graph && result.graph.goal_id !== goalId)
|| (result.goal_map && result.goal_map.goal_id !== goalId)
|| (result.acceptance && result.acceptance.goal_id !== goalId)) {
throw new Error("Review source does not match the selected Goal");
}
Expand Down Expand Up @@ -108,6 +138,7 @@ export type ReviewExportLabels = {
owner: string; reason: string; historical: string; checks: string; missingSources: string; observedScope: string;
kind: Record<ReviewNode["kind"], string>; state: Record<ReviewNode["state"], string>;
relation: Record<ReviewRelation, string>;
workMap: { title: string; boundary: string; kind: Record<GoalMapNode["kind"], string>; relation: Record<GoalMapEdge["relation"], string> } & Record<string, unknown>;
};

/** Export the entire validated snapshot, never the search-filtered screen. */
Expand All @@ -130,6 +161,14 @@ export function deliveryReviewMarkdown(snapshot: DeliveryReviewSnapshot, labels:
rows.push("", `## ${labels.relations}`, "");
for (const edge of graph.edges) rows.push(`- ${line(names.get(edge.from_node_id))} → ${labels.relation[edge.relation]} → ${line(names.get(edge.to_node_id))}: ${line(edge.reason)} (${line(edge.edge_id)})`, ` ${labels.refs}: ${line(JSON.stringify(edge.refs ?? {}))}`);
}
const map = snapshot.goal_map;
if (map) {
rows.push("", `## ${labels.workMap.title}`, "", labels.workMap.boundary, "", "```json", JSON.stringify(map.limits, null, 2), "```", "");
const names = new Map(map.nodes.map(node => [node.node_id, node.title]));
for (const node of map.nodes) rows.push(`- ${line(node.title)} · ${labels.workMap.kind[node.kind]} · ${labels.state[node.state]}${node.owner_agent ? ` · ${line(node.owner_agent)}` : ""} (${line(node.refs.todo_ids?.join(", "))})`);
if (map.edges.length) rows.push("");
for (const edge of map.edges) rows.push(`- ${line(names.get(edge.from_node_id))} → ${labels.workMap.relation[edge.relation]} → ${line(names.get(edge.to_node_id))}`);
}
rows.push("", `## ${labels.acceptance}`, "");
const acceptance = snapshot.acceptance;
if (!acceptance) rows.push(labels.unavailable);
Expand Down
Loading
Loading