zob-harness 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.pi/capabilities/zob-public-runtime-capabilities.json +16 -17
  2. package/.pi/extensions/zob-harness/index.ts +1 -1
  3. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/dry-run.ts +1107 -0
  4. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/report-writers.ts +325 -0
  5. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/smoke-run.ts +1286 -0
  6. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/types.ts +30 -0
  7. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/validation.ts +184 -0
  8. package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime.ts +4 -2912
  9. package/.pi/extensions/zob-harness/src/domains/compute/compute-profile.ts +2 -1
  10. package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts +24 -3
  11. package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/types.ts +1 -0
  12. package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts +5 -3
  13. package/.pi/extensions/zob-harness/src/domains/delegation/child-runner.ts +28 -3
  14. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/constants.ts +19 -0
  15. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/formatting.ts +148 -0
  16. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/normalize.ts +476 -0
  17. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/operations.ts +393 -0
  18. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/parsing.ts +277 -0
  19. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/reducer.ts +110 -0
  20. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos.ts +6 -1429
  21. package/.pi/extensions/zob-harness/src/domains/governance/governed-requests.ts +3 -1
  22. package/.pi/extensions/zob-harness/src/domains/governance/merge-queue.ts +3 -1
  23. package/.pi/extensions/zob-harness/src/domains/governance/sandbox/helpers.ts +124 -0
  24. package/.pi/extensions/zob-harness/src/domains/governance/sandbox/runners.ts +444 -0
  25. package/.pi/extensions/zob-harness/src/domains/governance/sandbox/simulation.ts +569 -0
  26. package/.pi/extensions/zob-harness/src/domains/governance/sandbox/types.ts +127 -0
  27. package/.pi/extensions/zob-harness/src/domains/governance/sandbox/validation.ts +273 -0
  28. package/.pi/extensions/zob-harness/src/domains/governance/sandbox.ts +4 -1508
  29. package/.pi/extensions/zob-harness/src/domains/governance/worker-pool.ts +3 -1
  30. package/.pi/extensions/zob-harness/src/domains/governance/workspace-claims.ts +3 -1
  31. package/.pi/extensions/zob-harness/src/domains/orchestration/room.ts +8 -2
  32. package/.pi/extensions/zob-harness/src/domains/promotion/coms.ts +8 -1
  33. package/.pi/extensions/zob-harness/src/runtime/commands/autonomy.ts +188 -0
  34. package/.pi/extensions/zob-harness/src/runtime/commands/compute.ts +165 -0
  35. package/.pi/extensions/zob-harness/src/runtime/commands/daemon.ts +191 -0
  36. package/.pi/extensions/zob-harness/src/runtime/commands/delegates.ts +47 -0
  37. package/.pi/extensions/zob-harness/src/runtime/commands/goal.ts +70 -0
  38. package/.pi/extensions/zob-harness/src/runtime/commands/intent.ts +383 -0
  39. package/.pi/extensions/zob-harness/src/runtime/commands/misc.ts +229 -0
  40. package/.pi/extensions/zob-harness/src/runtime/commands/project-dna.ts +130 -0
  41. package/.pi/extensions/zob-harness/src/runtime/commands/types.ts +3 -0
  42. package/.pi/extensions/zob-harness/src/runtime/commands/zcommit.ts +145 -0
  43. package/.pi/extensions/zob-harness/src/runtime/commands/zlive.ts +1606 -0
  44. package/.pi/extensions/zob-harness/src/runtime/commands/zmode.ts +42 -0
  45. package/.pi/extensions/zob-harness/src/runtime/commands.ts +26 -3109
  46. package/.pi/extensions/zob-harness/src/runtime/events.ts +67 -33
  47. package/.pi/extensions/zob-harness/src/runtime/goal-runtime/commands.ts +194 -0
  48. package/.pi/extensions/zob-harness/src/runtime/goal-runtime/events.ts +81 -0
  49. package/.pi/extensions/zob-harness/src/runtime/goal-runtime/state.ts +662 -0
  50. package/.pi/extensions/zob-harness/src/runtime/goal-runtime/tools.ts +1005 -0
  51. package/.pi/extensions/zob-harness/src/runtime/goal-runtime.ts +5 -1949
  52. package/.pi/extensions/zob-harness/src/runtime/tools-delegation/helpers.ts +786 -0
  53. package/.pi/extensions/zob-harness/src/runtime/tools-delegation/register.ts +1120 -0
  54. package/.pi/extensions/zob-harness/src/runtime/tools-delegation/types.ts +77 -0
  55. package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1 -1904
  56. package/.pi/extensions/zob-harness/src/runtime/zob-intro.ts +46 -15
  57. package/.pi/factories/project-dna/batch-manifest.json +1 -1
  58. package/.pi/factories/project-dna/pilot-manifest.json +1 -1
  59. package/.pi/factories/project-dna/smoke-manifest.json +1 -1
  60. package/README.md +29 -8
  61. package/package.json +14 -5
  62. package/scripts/git-ops/commit-policy-smoke.mjs +33 -6
  63. package/scripts/goal-todo/child-goal-ref-smoke.mjs +30 -3
  64. package/scripts/goal-todo/handoff-static-smoke.mjs +31 -3
  65. package/scripts/harness-intake/lib/cli-io.mjs +89 -0
  66. package/scripts/harness-intake/lib/constants.mjs +59 -0
  67. package/scripts/harness-intake/lib/infer-spec.mjs +127 -0
  68. package/scripts/harness-intake/lib/profiles.mjs +458 -0
  69. package/scripts/harness-intake/lib/run-init.mjs +307 -0
  70. package/scripts/harness-intake/lib/scan.mjs +266 -0
  71. package/scripts/harness-intake/lib/tmux.mjs +92 -0
  72. package/scripts/harness-intake/lib/validate.mjs +152 -0
  73. package/scripts/harness-intake/lib.mjs +8 -1521
  74. package/scripts/harness-switch/static-smoke.mjs +1 -1
  75. package/scripts/model-catalog/validate-economy.mjs +3 -1
  76. package/scripts/model-catalog/validate.mjs +3 -1
  77. package/scripts/project-dna/scan/scan.mjs +5 -2
  78. package/scripts/project-dna/scan/validate-scan-artifacts.mjs +3 -1
  79. package/scripts/project-dna/validation/validate-ontology.mjs +3 -1
  80. package/scripts/project-dna/validation/validate-scaffold.mjs +2 -2
  81. package/scripts/zagent-static-smoke.mjs +30 -2
  82. package/scripts/zpeer-local-e2e-smoke.mjs +18 -0
  83. package/scripts/zpeer-static-smoke.mjs +40 -5
  84. package/scripts/zteam-hot-add/smoke.mjs +30 -2
@@ -0,0 +1,476 @@
1
+ import type { GoalRoomTodoReducerDecision, GoalTodoArtifacts, GoalTodoClaimRef, GoalTodoClaimValidationRef, GoalTodoDelegationRef, GoalTodoEvent, GoalTodoNode, GoalTodoPolicy, GoalTodoState, GoalTodoStatus } from "../goal-todo-types.js";
2
+ import { type ZcommitChildChangedPathRef } from "../../git/git-ops.js";
3
+ import { isRecord } from "../../../core/utils/records.js";
4
+ import { VALID_CHILD_GOAL_STATUS, VALID_DELEGATION_STATUS, VALID_OWNER, VALID_PRIORITY, VALID_STATUS, VALID_STATUS_CLAIM, VALID_TARGET_READINESS, VALID_VALIDATION_ACTION, VALID_VALIDATION_CONFIDENCE, VALID_VALIDATION_STATUS, VALID_VALIDATION_VERDICT, ZOB_GOAL_TODO_ENTRY_TYPE } from "./constants.js";
5
+ import { hasOnlyNoneLike, renumberGoalPaths } from "./operations.js";
6
+
7
+ export function defaultGoalTodoPolicy(): GoalTodoPolicy {
8
+ return {
9
+ maxTodoDepth: 6,
10
+ maxDelegationDepth: 4,
11
+ maxChildrenPerTodo: 8,
12
+ maxOpenTodos: 80,
13
+ requireEvidenceForCritical: true,
14
+ parentOwnedClaims: true,
15
+ oracleBeforeGoalComplete: true,
16
+ };
17
+ }
18
+
19
+ export function createGoalTodoState(): GoalTodoState {
20
+ return { nodes: [], policy: defaultGoalTodoPolicy() };
21
+ }
22
+
23
+ export function unixSeconds(): number {
24
+ return Math.floor(Date.now() / 1000);
25
+ }
26
+
27
+ export function includesString<T extends string>(values: readonly T[], value: unknown): value is T {
28
+ return typeof value === "string" && (values as readonly string[]).includes(value);
29
+ }
30
+
31
+ export function stringArray(value: unknown): string[] {
32
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.trim().length > 0) : [];
33
+ }
34
+
35
+ export function numberField(record: Record<string, unknown>, key: string): number | undefined {
36
+ const value = record[key];
37
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
38
+ }
39
+
40
+ export function cloneNode(node: GoalTodoNode): GoalTodoNode {
41
+ return {
42
+ ...node,
43
+ acceptanceCriteria: [...node.acceptanceCriteria],
44
+ evidenceRefs: [...node.evidenceRefs],
45
+ validationCommands: [...node.validationCommands],
46
+ delegation: node.delegation ? { ...node.delegation } : undefined,
47
+ claim: node.claim ? { ...node.claim, acceptanceBlockers: [...node.claim.acceptanceBlockers], childChangedPaths: node.claim.childChangedPaths ? node.claim.childChangedPaths.map((ref) => ({ ...ref })) : undefined } : undefined,
48
+ validation: node.validation ? { ...node.validation, evidenceRefs: [...node.validation.evidenceRefs], validationCommands: [...node.validation.validationCommands], blockingIssues: [...node.validation.blockingIssues] } : undefined,
49
+ artifacts: node.artifacts
50
+ ? {
51
+ reports: node.artifacts.reports ? [...node.artifacts.reports] : undefined,
52
+ checkpoints: node.artifacts.checkpoints ? [...node.artifacts.checkpoints] : undefined,
53
+ sentinels: node.artifacts.sentinels ? [...node.artifacts.sentinels] : undefined,
54
+ taskHash: node.artifacts.taskHash,
55
+ outputHash: node.artifacts.outputHash,
56
+ }
57
+ : undefined,
58
+ citations: node.citations ? [...node.citations] : undefined,
59
+ };
60
+ }
61
+
62
+ export function clonePolicy(policy: GoalTodoPolicy): GoalTodoPolicy {
63
+ return { ...policy };
64
+ }
65
+
66
+ export function normalizePolicy(value: unknown): GoalTodoPolicy | undefined {
67
+ if (!isRecord(value)) return undefined;
68
+ return {
69
+ maxTodoDepth: Math.max(1, Math.trunc(numberField(value, "maxTodoDepth") ?? defaultGoalTodoPolicy().maxTodoDepth)),
70
+ maxDelegationDepth: Math.max(1, Math.trunc(numberField(value, "maxDelegationDepth") ?? defaultGoalTodoPolicy().maxDelegationDepth)),
71
+ maxChildrenPerTodo: Math.max(1, Math.trunc(numberField(value, "maxChildrenPerTodo") ?? defaultGoalTodoPolicy().maxChildrenPerTodo)),
72
+ maxOpenTodos: Math.max(1, Math.trunc(numberField(value, "maxOpenTodos") ?? defaultGoalTodoPolicy().maxOpenTodos)),
73
+ requireEvidenceForCritical: true,
74
+ parentOwnedClaims: true,
75
+ oracleBeforeGoalComplete: true,
76
+ };
77
+ }
78
+
79
+ export function normalizeDelegation(value: unknown): GoalTodoDelegationRef | undefined {
80
+ if (!isRecord(value)) return undefined;
81
+ const status = includesString(VALID_DELEGATION_STATUS, value.status) ? value.status : "running";
82
+ return {
83
+ runId: typeof value.runId === "string" ? value.runId : undefined,
84
+ agent: typeof value.agent === "string" ? value.agent : undefined,
85
+ childGoalId: typeof value.childGoalId === "string" ? value.childGoalId : undefined,
86
+ requestId: typeof value.requestId === "string" ? value.requestId : undefined,
87
+ delegationDepth: Math.max(0, Math.trunc(numberField(value, "delegationDepth") ?? 0)),
88
+ status,
89
+ };
90
+ }
91
+
92
+ export function normalizeArtifacts(value: unknown): GoalTodoArtifacts | undefined {
93
+ if (!isRecord(value)) return undefined;
94
+ return {
95
+ reports: stringArray(value.reports),
96
+ checkpoints: stringArray(value.checkpoints),
97
+ sentinels: stringArray(value.sentinels),
98
+ taskHash: typeof value.taskHash === "string" ? value.taskHash : undefined,
99
+ outputHash: typeof value.outputHash === "string" ? value.outputHash : undefined,
100
+ };
101
+ }
102
+
103
+ export function normalizeChildChangedPathRefs(value: unknown): ZcommitChildChangedPathRef[] {
104
+ if (!Array.isArray(value)) return [];
105
+ return value.flatMap((item) => {
106
+ if (!isRecord(item) || typeof item.path !== "string" || typeof item.pathHash !== "string" || typeof item.status !== "string") return [];
107
+ return [{ path: item.path, pathHash: item.pathHash, status: item.status, contentHash: typeof item.contentHash === "string" ? item.contentHash : undefined }];
108
+ }).slice(0, 100);
109
+ }
110
+
111
+ export function normalizeClaim(value: unknown): GoalTodoClaimRef | undefined {
112
+ if (!isRecord(value) || typeof value.claimHash !== "string") return undefined;
113
+ return {
114
+ claimHash: value.claimHash,
115
+ runId: typeof value.runId === "string" ? value.runId : undefined,
116
+ outputHash: typeof value.outputHash === "string" ? value.outputHash : undefined,
117
+ outputContract: typeof value.outputContract === "string" ? value.outputContract : undefined,
118
+ gatePassed: typeof value.gatePassed === "boolean" ? value.gatePassed : undefined,
119
+ childGoalStatus: includesString(VALID_CHILD_GOAL_STATUS, value.childGoalStatus) ? value.childGoalStatus : undefined,
120
+ statusClaim: includesString(VALID_STATUS_CLAIM, value.statusClaim) ? value.statusClaim : undefined,
121
+ targetReadiness: includesString(VALID_TARGET_READINESS, value.targetReadiness) ? value.targetReadiness : undefined,
122
+ acceptanceBlockers: stringArray(value.acceptanceBlockers),
123
+ noShip: typeof value.noShip === "boolean" ? value.noShip : undefined,
124
+ childChangedPaths: normalizeChildChangedPathRefs(value.childChangedPaths),
125
+ returnedAt: Math.trunc(numberField(value, "returnedAt") ?? unixSeconds()),
126
+ };
127
+ }
128
+
129
+ export function normalizeValidation(value: unknown): GoalTodoClaimValidationRef | undefined {
130
+ if (!isRecord(value)) return undefined;
131
+ const status = includesString(VALID_VALIDATION_STATUS, value.status) ? value.status : undefined;
132
+ if (!status) return undefined;
133
+ return {
134
+ runId: typeof value.runId === "string" ? value.runId : undefined,
135
+ agent: typeof value.agent === "string" ? value.agent : undefined,
136
+ status,
137
+ verdict: includesString(VALID_VALIDATION_VERDICT, value.verdict) ? value.verdict : undefined,
138
+ recommendedAction: includesString(VALID_VALIDATION_ACTION, value.recommendedAction) ? value.recommendedAction : undefined,
139
+ noShip: typeof value.noShip === "boolean" ? value.noShip : undefined,
140
+ outputHash: typeof value.outputHash === "string" ? value.outputHash : undefined,
141
+ evidenceRefs: stringArray(value.evidenceRefs),
142
+ validationCommands: stringArray(value.validationCommands),
143
+ blockingIssues: stringArray(value.blockingIssues),
144
+ confidence: includesString(VALID_VALIDATION_CONFIDENCE, value.confidence) ? value.confidence : undefined,
145
+ requestedAt: typeof value.requestedAt === "number" ? Math.trunc(value.requestedAt) : undefined,
146
+ validatedAt: typeof value.validatedAt === "number" ? Math.trunc(value.validatedAt) : undefined,
147
+ };
148
+ }
149
+
150
+ export function cloneValidation(validation: GoalTodoClaimValidationRef): GoalTodoClaimValidationRef {
151
+ return { ...validation, evidenceRefs: [...validation.evidenceRefs], validationCommands: [...validation.validationCommands], blockingIssues: [...validation.blockingIssues] };
152
+ }
153
+
154
+ export function cloneClaim(claim: GoalTodoClaimRef): GoalTodoClaimRef {
155
+ return { ...claim, acceptanceBlockers: [...claim.acceptanceBlockers] };
156
+ }
157
+
158
+ export function normalizeNode(value: unknown): GoalTodoNode | undefined {
159
+ if (!isRecord(value)) return undefined;
160
+ if (typeof value.id !== "string" || typeof value.goalId !== "string" || typeof value.title !== "string") return undefined;
161
+ const status = includesString(VALID_STATUS, value.status) ? value.status : "planned";
162
+ const owner = includesString(VALID_OWNER, value.owner) ? value.owner : "agent";
163
+ const priority = includesString(VALID_PRIORITY, value.priority) ? value.priority : "normal";
164
+ const now = unixSeconds();
165
+ return {
166
+ id: value.id,
167
+ goalId: value.goalId,
168
+ parentId: typeof value.parentId === "string" ? value.parentId : undefined,
169
+ path: typeof value.path === "string" && value.path.trim().length > 0 ? value.path : "1",
170
+ depth: Math.max(1, Math.trunc(numberField(value, "depth") ?? 1)),
171
+ title: value.title,
172
+ descriptionHash: typeof value.descriptionHash === "string" ? value.descriptionHash : undefined,
173
+ status,
174
+ owner,
175
+ required: value.required !== false,
176
+ priority,
177
+ acceptanceCriteria: stringArray(value.acceptanceCriteria),
178
+ evidenceRefs: stringArray(value.evidenceRefs),
179
+ validationCommands: stringArray(value.validationCommands),
180
+ delegation: normalizeDelegation(value.delegation),
181
+ claim: normalizeClaim(value.claim),
182
+ validation: normalizeValidation(value.validation),
183
+ artifacts: normalizeArtifacts(value.artifacts),
184
+ contextScopeId: typeof value.contextScopeId === "string" ? value.contextScopeId : undefined,
185
+ contextPackRef: typeof value.contextPackRef === "string" ? value.contextPackRef : undefined,
186
+ citations: stringArray(value.citations),
187
+ freshness: typeof value.freshness === "string" ? value.freshness : undefined,
188
+ blocker: typeof value.blocker === "string" ? value.blocker : undefined,
189
+ skipReason: typeof value.skipReason === "string" ? value.skipReason : undefined,
190
+ reviewNoShip: value.reviewNoShip === true,
191
+ createdAt: Math.trunc(numberField(value, "createdAt") ?? now),
192
+ updatedAt: Math.trunc(numberField(value, "updatedAt") ?? now),
193
+ };
194
+ }
195
+
196
+ export function normalizePatch(value: Partial<GoalTodoNode>): Partial<GoalTodoNode> {
197
+ const patch: Partial<GoalTodoNode> = {};
198
+ if ("parentId" in value && (typeof value.parentId === "string" || value.parentId === undefined)) patch.parentId = value.parentId;
199
+ if (typeof value.path === "string") patch.path = value.path;
200
+ if (typeof value.depth === "number" && Number.isFinite(value.depth)) patch.depth = Math.max(1, Math.trunc(value.depth));
201
+ if (typeof value.title === "string" && value.title.trim().length > 0) patch.title = value.title.trim();
202
+ if ("descriptionHash" in value && (typeof value.descriptionHash === "string" || value.descriptionHash === undefined)) patch.descriptionHash = value.descriptionHash;
203
+ if (includesString(VALID_STATUS, value.status)) patch.status = value.status;
204
+ if (includesString(VALID_OWNER, value.owner)) patch.owner = value.owner;
205
+ if (typeof value.required === "boolean") patch.required = value.required;
206
+ if (includesString(VALID_PRIORITY, value.priority)) patch.priority = value.priority;
207
+ if (Array.isArray(value.acceptanceCriteria)) patch.acceptanceCriteria = value.acceptanceCriteria.filter((item): item is string => typeof item === "string");
208
+ if (Array.isArray(value.evidenceRefs)) patch.evidenceRefs = value.evidenceRefs.filter((item): item is string => typeof item === "string");
209
+ if (Array.isArray(value.validationCommands)) patch.validationCommands = value.validationCommands.filter((item): item is string => typeof item === "string");
210
+ if (value.delegation) patch.delegation = { ...value.delegation };
211
+ if (value.claim) patch.claim = cloneClaim(value.claim);
212
+ else if ("claim" in value && value.claim === undefined) patch.claim = undefined;
213
+ if (value.validation) patch.validation = cloneValidation(value.validation);
214
+ else if ("validation" in value && value.validation === undefined) patch.validation = undefined;
215
+ if (value.artifacts) patch.artifacts = { ...value.artifacts };
216
+ if ("contextScopeId" in value && (typeof value.contextScopeId === "string" || value.contextScopeId === undefined)) patch.contextScopeId = value.contextScopeId;
217
+ if ("contextPackRef" in value && (typeof value.contextPackRef === "string" || value.contextPackRef === undefined)) patch.contextPackRef = value.contextPackRef;
218
+ if (Array.isArray(value.citations)) patch.citations = value.citations.filter((item): item is string => typeof item === "string");
219
+ if ("freshness" in value && (typeof value.freshness === "string" || value.freshness === undefined)) patch.freshness = value.freshness;
220
+ if ("blocker" in value && (typeof value.blocker === "string" || value.blocker === undefined)) patch.blocker = value.blocker;
221
+ if ("skipReason" in value && (typeof value.skipReason === "string" || value.skipReason === undefined)) patch.skipReason = value.skipReason;
222
+ if ("reviewNoShip" in value && (typeof value.reviewNoShip === "boolean" || value.reviewNoShip === undefined)) patch.reviewNoShip = value.reviewNoShip;
223
+ return patch;
224
+ }
225
+
226
+ export function applyPatchToNode(node: GoalTodoNode, patch: Partial<GoalTodoNode>): GoalTodoNode {
227
+ return {
228
+ ...node,
229
+ ...normalizePatch(patch),
230
+ acceptanceCriteria: patch.acceptanceCriteria ? [...patch.acceptanceCriteria] : [...node.acceptanceCriteria],
231
+ evidenceRefs: patch.evidenceRefs ? [...patch.evidenceRefs] : [...node.evidenceRefs],
232
+ validationCommands: patch.validationCommands ? [...patch.validationCommands] : [...node.validationCommands],
233
+ delegation: patch.delegation ? { ...patch.delegation } : patch.delegation === undefined ? node.delegation ? { ...node.delegation } : undefined : undefined,
234
+ claim: patch.claim ? cloneClaim(patch.claim) : patch.claim === undefined ? node.claim ? cloneClaim(node.claim) : undefined : undefined,
235
+ validation: patch.validation ? cloneValidation(patch.validation) : patch.validation === undefined ? node.validation ? cloneValidation(node.validation) : undefined : undefined,
236
+ artifacts: patch.artifacts ? { ...patch.artifacts } : patch.artifacts === undefined ? node.artifacts ? { ...node.artifacts } : undefined : undefined,
237
+ citations: patch.citations ? [...patch.citations] : node.citations ? [...node.citations] : undefined,
238
+ updatedAt: unixSeconds(),
239
+ };
240
+ }
241
+
242
+ export function replaceNode(state: GoalTodoState, node: GoalTodoNode): void {
243
+ const index = state.nodes.findIndex((candidate) => candidate.id === node.id && candidate.goalId === node.goalId);
244
+ if (index >= 0) state.nodes[index] = cloneNode(node);
245
+ else state.nodes.push(cloneNode(node));
246
+ }
247
+
248
+ export function removeGoalNodes(state: GoalTodoState, goalId: string): void {
249
+ state.nodes = state.nodes.filter((node) => node.goalId !== goalId);
250
+ if (state.focusTodoId && !state.nodes.some((node) => node.id === state.focusTodoId)) state.focusTodoId = undefined;
251
+ }
252
+
253
+ export function applyEvent(state: GoalTodoState, event: GoalTodoEvent): void {
254
+ if (event.kind === "policy_set") {
255
+ state.policy = clonePolicy(event.policy);
256
+ return;
257
+ }
258
+ if (event.kind === "clear_goal_todos") {
259
+ removeGoalNodes(state, event.goalId);
260
+ return;
261
+ }
262
+ if (event.kind === "snapshot") {
263
+ removeGoalNodes(state, event.goalId);
264
+ for (const node of event.nodes) replaceNode(state, node);
265
+ if (event.policy) state.policy = clonePolicy(event.policy);
266
+ state.focusTodoId = event.focusTodoId;
267
+ return;
268
+ }
269
+ if (event.kind === "add") {
270
+ replaceNode(state, event.node);
271
+ return;
272
+ }
273
+ if (event.kind === "patch") {
274
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
275
+ if (existing) replaceNode(state, applyPatchToNode(existing, event.patch));
276
+ return;
277
+ }
278
+ if (event.kind === "move") {
279
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
280
+ if (existing) replaceNode(state, applyPatchToNode(existing, { parentId: event.parentId }));
281
+ renumberGoalPaths(state, event.goalId);
282
+ return;
283
+ }
284
+ if (event.kind === "split") {
285
+ const parent = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
286
+ if (parent) replaceNode(state, applyPatchToNode(parent, { status: "in_progress" }));
287
+ renumberGoalPaths(state, event.goalId);
288
+ return;
289
+ }
290
+ if (event.kind === "delegate_link") {
291
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
292
+ if (existing) replaceNode(state, applyPatchToNode(existing, { status: "delegated", owner: "subagent", delegation: event.delegation, blocker: undefined }));
293
+ return;
294
+ }
295
+ if (event.kind === "claim_returned") {
296
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
297
+ if (existing) {
298
+ const evidenceRefs = [...new Set([...existing.evidenceRefs, ...event.evidenceRefs])];
299
+ const validationCommands = [...new Set([...existing.validationCommands, ...event.validationCommands])];
300
+ const claim: GoalTodoClaimRef = {
301
+ claimHash: event.claimHash,
302
+ runId: event.runId,
303
+ outputHash: event.outputHash,
304
+ outputContract: event.outputContract,
305
+ gatePassed: event.gatePassed,
306
+ childGoalStatus: event.childGoalStatus,
307
+ statusClaim: event.statusClaim,
308
+ targetReadiness: event.targetReadiness,
309
+ acceptanceBlockers: event.acceptanceBlockers ?? [],
310
+ noShip: event.noShip,
311
+ childChangedPaths: event.childChangedPaths ?? [],
312
+ returnedAt: event.at,
313
+ };
314
+ const claimStatus: GoalTodoStatus = event.statusClaim === "blocked"
315
+ ? "blocked"
316
+ : event.statusClaim === "incomplete" || event.noShip === true || (event.acceptanceBlockers ?? []).length > 0 || event.targetReadiness === "needs_parent_review" || event.targetReadiness === "blocked"
317
+ ? "needs_review"
318
+ : "claim_returned";
319
+ replaceNode(state, applyPatchToNode(existing, {
320
+ status: claimStatus,
321
+ owner: claimStatus === "claim_returned" ? existing.owner : "agent",
322
+ evidenceRefs,
323
+ validationCommands,
324
+ delegation: existing.delegation ? { ...existing.delegation, status: "claim_returned" } : undefined,
325
+ claim,
326
+ artifacts: { ...(existing.artifacts ?? {}), outputHash: event.outputHash ?? event.claimHash },
327
+ blocker: claimStatus === "claim_returned" ? undefined : event.acceptanceBlockers?.[0] ?? (event.noShip === true ? "delegated claim returned advisory no_ship=true; parent review required" : `delegated claim status ${event.statusClaim ?? "needs_review"}; parent review required`),
328
+ reviewNoShip: claimStatus === "claim_returned" ? undefined : true,
329
+ }));
330
+ }
331
+ return;
332
+ }
333
+ if (event.kind === "claim_validation_requested") {
334
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
335
+ if (existing) replaceNode(state, applyPatchToNode(existing, {
336
+ status: "needs_oracle",
337
+ owner: "oracle",
338
+ validation: cloneValidation(event.validation),
339
+ blocker: undefined,
340
+ reviewNoShip: undefined,
341
+ }));
342
+ return;
343
+ }
344
+ if (event.kind === "claim_validation_returned") {
345
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
346
+ if (existing) {
347
+ const evidenceRefs = [...new Set([...existing.evidenceRefs, ...event.evidenceRefs])];
348
+ const validationCommands = [...new Set([...existing.validationCommands, ...event.validationCommands])];
349
+ const passed = event.validation.status === "passed" && event.validation.verdict === "PASS" && event.validation.noShip === false && hasOnlyNoneLike(event.validation.blockingIssues);
350
+ const warned = event.validation.status === "warn" && event.validation.verdict === "WARN" && event.validation.noShip === false && hasOnlyNoneLike(event.validation.blockingIssues);
351
+ replaceNode(state, applyPatchToNode(existing, {
352
+ status: passed ? "claim_returned" : warned ? "needs_review" : "blocked",
353
+ owner: passed ? "subagent" : warned ? "agent" : existing.owner,
354
+ evidenceRefs,
355
+ validationCommands,
356
+ validation: cloneValidation(event.validation),
357
+ blocker: passed ? undefined : event.validation.blockingIssues[0] ?? `claim validation ${event.validation.verdict ?? "blocked"}`,
358
+ reviewNoShip: passed ? undefined : true,
359
+ }));
360
+ }
361
+ return;
362
+ }
363
+ if (event.kind === "claim_accepted") {
364
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
365
+ if (existing) replaceNode(state, applyPatchToNode(existing, {
366
+ status: "done",
367
+ evidenceRefs: [...new Set([...existing.evidenceRefs, ...event.evidenceRefs])],
368
+ validationCommands: [...new Set([...existing.validationCommands, ...event.validationCommands])],
369
+ delegation: existing.delegation ? { ...existing.delegation, status: "accepted" } : undefined,
370
+ blocker: undefined,
371
+ reviewNoShip: undefined,
372
+ }));
373
+ return;
374
+ }
375
+ if (event.kind === "claim_rejected") {
376
+ const existing = state.nodes.find((node) => node.goalId === event.goalId && node.id === event.todoId);
377
+ if (existing) replaceNode(state, applyPatchToNode(existing, {
378
+ status: "blocked",
379
+ delegation: existing.delegation ? { ...existing.delegation, status: "rejected" } : undefined,
380
+ blocker: `claim rejected (${event.reasonHash.slice(0, 12)})`,
381
+ reviewNoShip: true,
382
+ }));
383
+ return;
384
+ }
385
+ if (event.kind === "focus") state.focusTodoId = event.todoId;
386
+ }
387
+
388
+ export function normalizeEvent(value: unknown): GoalTodoEvent | undefined {
389
+ if (!isRecord(value) || value.version !== 1 || typeof value.kind !== "string" || typeof value.goalId !== "string") return undefined;
390
+ const source = value.source === "tool" || value.source === "runtime" || value.source === "delegation" || value.source === "import" ? value.source : "command";
391
+ const at = Math.trunc(numberField(value, "at") ?? unixSeconds());
392
+ if (value.kind === "policy_set") {
393
+ const policy = normalizePolicy(value.policy);
394
+ return policy ? { version: 1, kind: "policy_set", source, goalId: value.goalId, policy, at } : undefined;
395
+ }
396
+ if (value.kind === "add") {
397
+ const node = normalizeNode(value.node);
398
+ return node ? { version: 1, kind: "add", source, goalId: value.goalId, node, at } : undefined;
399
+ }
400
+ if (value.kind === "patch" && typeof value.todoId === "string" && isRecord(value.patch)) return { version: 1, kind: "patch", source, goalId: value.goalId, todoId: value.todoId, patch: value.patch as Partial<GoalTodoNode>, at };
401
+ if (value.kind === "move" && typeof value.todoId === "string") return { version: 1, kind: "move", source, goalId: value.goalId, todoId: value.todoId, parentId: typeof value.parentId === "string" ? value.parentId : undefined, at };
402
+ if (value.kind === "split" && typeof value.todoId === "string") return { version: 1, kind: "split", source, goalId: value.goalId, todoId: value.todoId, childIds: stringArray(value.childIds), at };
403
+ if (value.kind === "delegate_link" && typeof value.todoId === "string" && typeof value.runId === "string") {
404
+ const delegation = normalizeDelegation(value.delegation);
405
+ return delegation ? { version: 1, kind: "delegate_link", source, goalId: value.goalId, todoId: value.todoId, runId: value.runId, delegation, at } : undefined;
406
+ }
407
+ if (value.kind === "claim_returned" && typeof value.todoId === "string" && typeof value.claimHash === "string") return { version: 1, kind: "claim_returned", source, goalId: value.goalId, todoId: value.todoId, claimHash: value.claimHash, evidenceRefs: stringArray(value.evidenceRefs), validationCommands: stringArray(value.validationCommands), noShip: typeof value.noShip === "boolean" ? value.noShip : undefined, runId: typeof value.runId === "string" ? value.runId : undefined, outputHash: typeof value.outputHash === "string" ? value.outputHash : undefined, outputContract: typeof value.outputContract === "string" ? value.outputContract : undefined, gatePassed: typeof value.gatePassed === "boolean" ? value.gatePassed : undefined, childGoalStatus: includesString(VALID_CHILD_GOAL_STATUS, value.childGoalStatus) ? value.childGoalStatus : undefined, statusClaim: includesString(VALID_STATUS_CLAIM, value.statusClaim) ? value.statusClaim : undefined, targetReadiness: includesString(VALID_TARGET_READINESS, value.targetReadiness) ? value.targetReadiness : undefined, acceptanceBlockers: stringArray(value.acceptanceBlockers), childChangedPaths: normalizeChildChangedPathRefs(value.childChangedPaths), at };
408
+ if (value.kind === "claim_validation_requested" && typeof value.todoId === "string") {
409
+ const validation = normalizeValidation(value.validation);
410
+ return validation ? { version: 1, kind: "claim_validation_requested", source, goalId: value.goalId, todoId: value.todoId, validation, at } : undefined;
411
+ }
412
+ if (value.kind === "claim_validation_returned" && typeof value.todoId === "string") {
413
+ const validation = normalizeValidation(value.validation);
414
+ return validation ? { version: 1, kind: "claim_validation_returned", source, goalId: value.goalId, todoId: value.todoId, validation, evidenceRefs: stringArray(value.evidenceRefs), validationCommands: stringArray(value.validationCommands), noShip: typeof value.noShip === "boolean" ? value.noShip : undefined, at } : undefined;
415
+ }
416
+ if (value.kind === "claim_accepted" && typeof value.todoId === "string") return { version: 1, kind: "claim_accepted", source, goalId: value.goalId, todoId: value.todoId, evidenceRefs: stringArray(value.evidenceRefs), validationCommands: stringArray(value.validationCommands), at };
417
+ if (value.kind === "claim_rejected" && typeof value.todoId === "string" && typeof value.reasonHash === "string") return { version: 1, kind: "claim_rejected", source, goalId: value.goalId, todoId: value.todoId, reasonHash: value.reasonHash, at };
418
+ if (value.kind === "clear_goal_todos") return { version: 1, kind: "clear_goal_todos", source, goalId: value.goalId, at };
419
+ if (value.kind === "focus") return { version: 1, kind: "focus", source, goalId: value.goalId, todoId: typeof value.todoId === "string" ? value.todoId : undefined, at };
420
+ if (value.kind === "snapshot") {
421
+ const nodes = Array.isArray(value.nodes) ? value.nodes.map(normalizeNode).filter((node): node is GoalTodoNode => Boolean(node)) : [];
422
+ return { version: 1, kind: "snapshot", source, goalId: value.goalId, nodes, policy: normalizePolicy(value.policy), focusTodoId: typeof value.focusTodoId === "string" ? value.focusTodoId : undefined, at };
423
+ }
424
+ return undefined;
425
+ }
426
+
427
+ export function restoreGoalTodosFromBranch(entries: Iterable<unknown>): GoalTodoState {
428
+ const state = createGoalTodoState();
429
+ for (const entry of entries) {
430
+ if (!isRecord(entry) || entry.customType !== ZOB_GOAL_TODO_ENTRY_TYPE || !isRecord(entry.data)) continue;
431
+ const event = normalizeEvent(entry.data);
432
+ if (event) applyEvent(state, event);
433
+ }
434
+ return state;
435
+ }
436
+
437
+ export function goalRoomMetadata(message: Record<string, unknown>): Record<string, unknown> {
438
+ return isRecord(message.metadata) ? message.metadata : {};
439
+ }
440
+
441
+ export function reducerStringArray(value: unknown): string[] {
442
+ return stringArray(value).slice(0, 20);
443
+ }
444
+
445
+ export function goalRoomMessageString(message: Record<string, unknown>, key: string): string | undefined {
446
+ const value = message[key];
447
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
448
+ }
449
+
450
+ export function metadataString(metadata: Record<string, unknown>, key: string): string | undefined {
451
+ const value = metadata[key];
452
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
453
+ }
454
+
455
+ export function baseTodoReducerDecision(message: Record<string, unknown>, reasonCodes: string[] = []): GoalRoomTodoReducerDecision {
456
+ return {
457
+ schema: "zob.todo-event-reducer-decision.v1",
458
+ action: "ignore",
459
+ reasonCodes,
460
+ goalId: goalRoomMessageString(message, "goalId"),
461
+ todoId: goalRoomMessageString(message, "todoId"),
462
+ sourceMsgId: goalRoomMessageString(message, "msgId"),
463
+ sourceKind: goalRoomMessageString(message, "kind"),
464
+ runId: goalRoomMessageString(message, "runId"),
465
+ outputHash: goalRoomMessageString(message, "outputHash"),
466
+ evidenceRefs: reducerStringArray(message.evidenceRefs),
467
+ validationCommands: [],
468
+ acceptanceBlockers: [],
469
+ parentOwnedActions: true,
470
+ directMutationByWorker: false,
471
+ reducerRequiredForTodoMutation: true,
472
+ bodyStored: false,
473
+ promptBodiesStored: false,
474
+ outputBodiesStored: false,
475
+ };
476
+ }