let-them-talk 5.2.5 → 5.4.0

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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7214
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('path');
4
+
5
+ const schema = require(path.resolve(__dirname, '..', 'events', 'schema.js'));
6
+
7
+ const EXPECTED_EVENT_ENVELOPE_FIELDS = [
8
+ 'event_id',
9
+ 'stream',
10
+ 'branch_id',
11
+ 'seq',
12
+ 'type',
13
+ 'occurred_at',
14
+ 'schema_version',
15
+ 'actor_agent',
16
+ 'session_id',
17
+ 'command_id',
18
+ 'causation_id',
19
+ 'correlation_id',
20
+ 'payload',
21
+ ];
22
+
23
+ const EXPECTED_RUNTIME_GLOBAL_FAMILIES = [
24
+ 'agent',
25
+ 'profile',
26
+ 'lock',
27
+ 'branch',
28
+ 'migration',
29
+ ];
30
+
31
+ const EXPECTED_BRANCH_LOCAL_FAMILIES = [
32
+ 'session',
33
+ 'conversation',
34
+ 'message',
35
+ 'task',
36
+ 'workflow',
37
+ 'workspace',
38
+ 'decision',
39
+ 'kb',
40
+ 'review',
41
+ 'dependency',
42
+ 'vote',
43
+ 'rule',
44
+ 'progress',
45
+ 'evidence',
46
+ ];
47
+
48
+ const EXPECTED_EVENT_TYPES = [
49
+ 'agent.registered',
50
+ 'agent.unregistered',
51
+ 'agent.status_updated',
52
+ 'agent.heartbeat_recorded',
53
+ 'agent.branch_assigned',
54
+ 'agent.listening_updated',
55
+ 'profile.updated',
56
+ 'lock.acquired',
57
+ 'lock.released',
58
+ 'branch.created',
59
+ 'migration.started',
60
+ 'migration.completed',
61
+ 'migration.failed',
62
+ 'session.started',
63
+ 'session.resumed',
64
+ 'session.interrupted',
65
+ 'session.completed',
66
+ 'session.failed',
67
+ 'session.abandoned',
68
+ 'conversation.mode_updated',
69
+ 'conversation.channel_joined',
70
+ 'conversation.channel_left',
71
+ 'conversation.manager_claimed',
72
+ 'conversation.phase_updated',
73
+ 'conversation.floor_yielded',
74
+ 'message.sent',
75
+ 'message.corrected',
76
+ 'message.redacted',
77
+ 'task.created',
78
+ 'task.updated',
79
+ 'task.claimed',
80
+ 'task.completed',
81
+ 'workflow.created',
82
+ 'workflow.step_started',
83
+ 'workflow.step_completed',
84
+ 'workflow.step_reassigned',
85
+ 'workflow.completed',
86
+ 'workflow.paused',
87
+ 'workflow.resumed',
88
+ 'workflow.stopped',
89
+ 'workspace.written',
90
+ 'decision.logged',
91
+ 'kb.written',
92
+ 'review.requested',
93
+ 'review.submitted',
94
+ 'dependency.declared',
95
+ 'dependency.resolved',
96
+ 'vote.called',
97
+ 'vote.cast',
98
+ 'vote.resolved',
99
+ 'rule.added',
100
+ 'rule.toggled',
101
+ 'rule.removed',
102
+ 'progress.updated',
103
+ 'evidence.recorded',
104
+ ];
105
+
106
+ function fail(lines) {
107
+ console.error(lines.join('\n'));
108
+ process.exit(1);
109
+ }
110
+
111
+ function ensureIncludesAll(problems, actualValues, expectedValues, label) {
112
+ const actualSet = new Set(actualValues);
113
+ for (const expected of expectedValues) {
114
+ if (!actualSet.has(expected)) {
115
+ problems.push(`${label} is missing: ${expected}`);
116
+ }
117
+ }
118
+ }
119
+
120
+ function buildSampleEvent(type) {
121
+ const stream = schema.resolveTypeStream(type);
122
+ let payload = {};
123
+ if (type === 'message.sent') {
124
+ payload = {
125
+ message: {
126
+ id: 'msg_task3a_sample',
127
+ from: 'schema-alpha',
128
+ to: 'schema-beta',
129
+ content: 'schema sample message',
130
+ timestamp: '2026-04-15T00:00:00.000Z',
131
+ },
132
+ };
133
+ } else if (type === 'message.corrected') {
134
+ payload = {
135
+ message_id: 'msg_task3a_sample',
136
+ content: 'schema sample edit',
137
+ edited_at: '2026-04-15T00:01:00.000Z',
138
+ max_edit_history: 10,
139
+ };
140
+ } else if (type === 'message.redacted') {
141
+ payload = {
142
+ message_id: 'msg_task3a_sample',
143
+ redacted_at: '2026-04-15T00:02:00.000Z',
144
+ };
145
+ }
146
+
147
+ return {
148
+ event_id: 'evt_task3a_sample',
149
+ stream,
150
+ branch_id: stream === schema.EVENT_STREAMS.BRANCH ? 'main' : null,
151
+ seq: 1,
152
+ type,
153
+ occurred_at: '2026-04-15T00:00:00.000Z',
154
+ schema_version: schema.CANONICAL_EVENT_SCHEMA_VERSION,
155
+ actor_agent: 'task3a-validator',
156
+ session_id: stream === schema.EVENT_STREAMS.BRANCH ? 'session_task3a' : null,
157
+ command_id: 'cmd_task3a',
158
+ causation_id: null,
159
+ correlation_id: null,
160
+ payload,
161
+ extra_field_kept_for_future_replay: true,
162
+ };
163
+ }
164
+
165
+ function main() {
166
+ const problems = [];
167
+
168
+ ensureIncludesAll(
169
+ problems,
170
+ schema.REQUIRED_EVENT_ENVELOPE_FIELDS,
171
+ EXPECTED_EVENT_ENVELOPE_FIELDS,
172
+ 'Required event envelope field'
173
+ );
174
+
175
+ if (schema.DEFAULT_COLLABORATION_STREAM !== schema.EVENT_STREAMS.BRANCH) {
176
+ problems.push('Collaboration event default stream must be branch.');
177
+ }
178
+
179
+ ensureIncludesAll(
180
+ problems,
181
+ schema.EVENT_SCOPE_SPLIT.runtimeGlobalFamilies,
182
+ EXPECTED_RUNTIME_GLOBAL_FAMILIES,
183
+ 'Runtime-global family'
184
+ );
185
+
186
+ ensureIncludesAll(
187
+ problems,
188
+ schema.EVENT_SCOPE_SPLIT.branchLocalFamilies,
189
+ EXPECTED_BRANCH_LOCAL_FAMILIES,
190
+ 'Branch-local family'
191
+ );
192
+
193
+ for (const family of EXPECTED_RUNTIME_GLOBAL_FAMILIES) {
194
+ const definition = schema.EVENT_FAMILY_REGISTRY[family];
195
+ if (!definition) {
196
+ problems.push(`Missing family registry entry: ${family}`);
197
+ continue;
198
+ }
199
+ if (definition.scope !== schema.EVENT_STREAMS.RUNTIME) {
200
+ problems.push(`Family ${family} must resolve to runtime scope.`);
201
+ }
202
+ if (!Array.isArray(definition.types) || definition.types.length === 0) {
203
+ problems.push(`Family ${family} must register at least one event type.`);
204
+ }
205
+ }
206
+
207
+ for (const family of EXPECTED_BRANCH_LOCAL_FAMILIES) {
208
+ const definition = schema.EVENT_FAMILY_REGISTRY[family];
209
+ if (!definition) {
210
+ problems.push(`Missing family registry entry: ${family}`);
211
+ continue;
212
+ }
213
+ if (definition.scope !== schema.EVENT_STREAMS.BRANCH) {
214
+ problems.push(`Family ${family} must resolve to branch scope.`);
215
+ }
216
+ if (!Array.isArray(definition.types) || definition.types.length === 0) {
217
+ problems.push(`Family ${family} must register at least one event type.`);
218
+ }
219
+ }
220
+
221
+ ensureIncludesAll(
222
+ problems,
223
+ Object.keys(schema.EVENT_TYPE_REGISTRY),
224
+ EXPECTED_EVENT_TYPES,
225
+ 'Required canonical event type'
226
+ );
227
+
228
+ for (const type of EXPECTED_EVENT_TYPES) {
229
+ const definition = schema.EVENT_TYPE_REGISTRY[type];
230
+ if (!definition) continue;
231
+
232
+ const familyDefinition = schema.EVENT_FAMILY_REGISTRY[definition.family];
233
+ if (!familyDefinition) {
234
+ problems.push(`Event type ${type} points at unknown family ${definition.family}.`);
235
+ continue;
236
+ }
237
+
238
+ if (definition.scope !== familyDefinition.scope) {
239
+ problems.push(`Event type ${type} has scope ${definition.scope}, but family ${definition.family} is ${familyDefinition.scope}.`);
240
+ }
241
+
242
+ const validation = schema.validateCanonicalEvent(buildSampleEvent(type));
243
+ if (!validation.ok) {
244
+ problems.push(`Sample event for ${type} failed validation: ${validation.problems.join('; ')}`);
245
+ }
246
+ }
247
+
248
+ const invalidCorrectionValidation = schema.validateCanonicalEvent({
249
+ ...buildSampleEvent('message.corrected'),
250
+ payload: {
251
+ message_id: 'msg_task3a_sample',
252
+ content: 42,
253
+ },
254
+ });
255
+ if (invalidCorrectionValidation.ok || !invalidCorrectionValidation.problems.some((problem) => problem.includes('payload.content'))) {
256
+ problems.push('message.corrected payload validation must reject non-string payload.content values.');
257
+ }
258
+
259
+ if (problems.length > 0) {
260
+ fail([
261
+ 'Canonical event schema validation failed.',
262
+ ...problems.map((problem) => `- ${problem}`),
263
+ ]);
264
+ }
265
+
266
+ console.log([
267
+ 'Canonical event schema validation passed.',
268
+ `Envelope fields checked: ${EXPECTED_EVENT_ENVELOPE_FIELDS.length}`,
269
+ `Runtime-global families checked: ${EXPECTED_RUNTIME_GLOBAL_FAMILIES.length}`,
270
+ `Branch-local families checked: ${EXPECTED_BRANCH_LOCAL_FAMILIES.length}`,
271
+ `Seed event types checked: ${EXPECTED_EVENT_TYPES.length}`,
272
+ 'Branch-local collaboration default: branch',
273
+ ].join('\n'));
274
+ }
275
+
276
+ main();
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const { createCanonicalEventLog } = require(path.resolve(__dirname, '..', 'events', 'log.js'));
8
+ const { createCanonicalState, createBranchPathResolvers } = require(path.resolve(__dirname, '..', 'state', 'canonical.js'));
9
+
10
+ const SERVER_FILE = path.resolve(__dirname, '..', 'server.js');
11
+
12
+ function fail(lines, exitCode = 1) {
13
+ fs.writeSync(2, lines.join('\n') + '\n');
14
+ process.exit(exitCode);
15
+ }
16
+
17
+ function assert(condition, message, problems) {
18
+ if (!condition) problems.push(message);
19
+ }
20
+
21
+ function readJson(filePath) {
22
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
23
+ }
24
+
25
+ function extractBlock(source, startAnchor, endAnchor) {
26
+ const startIndex = source.indexOf(startAnchor);
27
+ if (startIndex === -1) return '';
28
+ const endIndex = endAnchor ? source.indexOf(endAnchor, startIndex + startAnchor.length) : source.length;
29
+ if (endIndex === -1) return source.slice(startIndex);
30
+ return source.slice(startIndex, endIndex);
31
+ }
32
+
33
+ function buildWorkflow(id, description) {
34
+ return {
35
+ id,
36
+ name: `Workflow ${id}`,
37
+ status: 'active',
38
+ created_by: 'alpha',
39
+ created_at: '2026-04-16T02:15:00.000Z',
40
+ updated_at: '2026-04-16T02:15:00.000Z',
41
+ steps: [
42
+ {
43
+ id: 1,
44
+ description,
45
+ assignee: 'alpha',
46
+ depends_on: [],
47
+ status: 'in_progress',
48
+ started_at: '2026-04-16T02:15:00.000Z',
49
+ completed_at: null,
50
+ notes: '',
51
+ },
52
+ {
53
+ id: 2,
54
+ description: `Follow-up for ${id}`,
55
+ assignee: 'beta',
56
+ depends_on: [1],
57
+ status: 'pending',
58
+ started_at: null,
59
+ completed_at: null,
60
+ notes: '',
61
+ },
62
+ ],
63
+ };
64
+ }
65
+
66
+ function buildTask(id, title) {
67
+ return {
68
+ id,
69
+ title,
70
+ description: `${title} description`,
71
+ status: 'pending',
72
+ assignee: 'alpha',
73
+ created_by: 'alpha',
74
+ created_at: '2026-04-16T02:15:00.000Z',
75
+ updated_at: '2026-04-16T02:15:00.000Z',
76
+ notes: [],
77
+ };
78
+ }
79
+
80
+ function main() {
81
+ const problems = [];
82
+ const serverSource = fs.readFileSync(SERVER_FILE, 'utf8');
83
+ const updateTaskBlock = extractBlock(serverSource, 'function toolUpdateTask(taskId, status, notes = null, evidence = null) {', 'function toolListTasks(status = null, assignee = null) {');
84
+ const advanceWorkflowBlock = extractBlock(serverSource, 'function toolAdvanceWorkflow(workflowId, notes, evidence = null) {', 'function toolWorkflowStatus(workflowId) {');
85
+ const verifyAndAdvanceBlock = extractBlock(serverSource, 'async function toolVerifyAndAdvance(params) {', 'function toolRetryWithImprovement(params) {');
86
+ const handoffBlock = extractBlock(serverSource, 'function emitWorkflowHandoffMessages(options = {}) {', '// --- Tool implementations ---');
87
+ const updateTaskSchemaBlock = extractBlock(serverSource, "name: 'update_task',", "name: 'list_tasks',");
88
+ const advanceWorkflowSchemaBlock = extractBlock(serverSource, "name: 'advance_workflow',", "name: 'workflow_status',");
89
+
90
+ assert(updateTaskBlock.includes('canonicalState.updateTaskStatus({'), 'toolUpdateTask() must route terminal completion through canonicalState.updateTaskStatus(...).', problems);
91
+ assert(advanceWorkflowBlock.includes('canonicalState.advanceWorkflow({'), 'toolAdvanceWorkflow() must route advancement through canonicalState.advanceWorkflow(...).', problems);
92
+ assert(verifyAndAdvanceBlock.includes('canonicalState.advanceWorkflow({'), 'toolVerifyAndAdvance() must route verified advancement through canonicalState.advanceWorkflow(...).', problems);
93
+ assert(!verifyAndAdvanceBlock.includes('currentStep.verification = {'), 'toolVerifyAndAdvance() must not write inline verification on the workflow step directly.', problems);
94
+ assert(handoffBlock.includes('evidence_ref: evidenceRef || null'), 'Workflow handoff messages must carry an evidence_ref field.', problems);
95
+ assert(handoffBlock.includes('session_id: currentSessionId || null'), 'Workflow handoff messages must carry a session_id field.', problems);
96
+ assert(handoffBlock.includes('canonicalState.appendMessage(msg, {'), 'Workflow handoff messages must use canonicalState.appendMessage(...) so canonical message metadata is preserved.', problems);
97
+ assert(updateTaskSchemaBlock.includes('evidence: COMPLETION_EVIDENCE_INPUT_SCHEMA'), 'update_task tool schema must expose the evidence input object for terminal transitions.', problems);
98
+ assert(advanceWorkflowSchemaBlock.includes('evidence: COMPLETION_EVIDENCE_INPUT_SCHEMA'), 'advance_workflow tool schema must expose the evidence input object.', problems);
99
+
100
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'letthemtalk-evidence-completion-'));
101
+ const dataDir = path.join(tempRoot, '.agent-bridge');
102
+ const branchName = 'feature_task5b';
103
+ const branchPaths = createBranchPathResolvers(dataDir);
104
+ const canonicalState = createCanonicalState({ dataDir, processPid: 5150 });
105
+ const eventLog = createCanonicalEventLog({ dataDir });
106
+
107
+ try {
108
+ fs.mkdirSync(dataDir, { recursive: true });
109
+ fs.writeFileSync(branchPaths.getWorkflowsFile(branchName), JSON.stringify([
110
+ buildWorkflow('wf_verify_path', 'Verify and advance path'),
111
+ buildWorkflow('wf_manual_advance', 'Manual advance path'),
112
+ buildWorkflow('wf_missing_evidence', 'Evidence-less path should fail'),
113
+ ], null, 2));
114
+ fs.writeFileSync(branchPaths.getTasksFile(branchName), JSON.stringify([
115
+ buildTask('task_missing_evidence', 'Task without evidence'),
116
+ buildTask('task_evidence_backed', 'Task with evidence'),
117
+ ], null, 2));
118
+
119
+ const missingAdvance = canonicalState.advanceWorkflow({
120
+ workflowId: 'wf_missing_evidence',
121
+ actor: 'alpha',
122
+ branch: branchName,
123
+ sessionId: 'session_alpha',
124
+ sourceTool: 'advance_workflow',
125
+ });
126
+ assert(!!missingAdvance.error, 'advanceWorkflow() must reject evidence-less workflow advancement.', problems);
127
+
128
+ const verifyAdvance = canonicalState.advanceWorkflow({
129
+ workflowId: 'wf_verify_path',
130
+ actor: 'alpha',
131
+ branch: branchName,
132
+ sessionId: 'session_alpha',
133
+ commandId: 'cmd_verify',
134
+ correlationId: 'wf_verify_path',
135
+ expectedAssignee: 'alpha',
136
+ sourceTool: 'verify_and_advance',
137
+ evidence: {
138
+ summary: 'Implemented the first evidence-backed verify path',
139
+ verification: 'Ran the deterministic evidence validation fixture',
140
+ files_changed: ['agent-bridge/server.js', 'agent-bridge/state/canonical.js'],
141
+ confidence: 88,
142
+ learnings: 'Keep evidence authoritative and thin.',
143
+ },
144
+ });
145
+ assert(verifyAdvance.success, 'advanceWorkflow() should succeed for the verify_and_advance evidence path.', problems);
146
+ assert(verifyAdvance.evidence_ref && verifyAdvance.evidence_ref.branch_id === branchName, 'Verified advancement must return a branch-scoped evidence reference.', problems);
147
+
148
+ const manualAdvance = canonicalState.advanceWorkflow({
149
+ workflowId: 'wf_manual_advance',
150
+ actor: 'alpha',
151
+ branch: branchName,
152
+ sessionId: 'session_alpha',
153
+ commandId: 'cmd_manual',
154
+ correlationId: 'wf_manual_advance',
155
+ sourceTool: 'advance_workflow',
156
+ evidence: {
157
+ summary: 'Manually advanced the workflow with evidence',
158
+ verification: 'Checked step state and next-step activation',
159
+ files_changed: ['agent-bridge/state/evidence.js'],
160
+ confidence: 91,
161
+ },
162
+ });
163
+ assert(manualAdvance.success, 'advanceWorkflow() should succeed for the manual advance_workflow evidence path.', problems);
164
+ assert(Array.isArray(manualAdvance.next_steps) && manualAdvance.next_steps.length === 1, 'Manual workflow advancement should activate the next ready step.', problems);
165
+
166
+ const missingTaskEvidence = canonicalState.updateTaskStatus({
167
+ taskId: 'task_missing_evidence',
168
+ status: 'done',
169
+ actor: 'alpha',
170
+ branch: branchName,
171
+ sessionId: 'session_alpha',
172
+ sourceTool: 'update_task',
173
+ });
174
+ assert(!!missingTaskEvidence.error, 'updateTaskStatus() must reject evidence-less terminal task completion.', problems);
175
+
176
+ const taskCompletion = canonicalState.updateTaskStatus({
177
+ taskId: 'task_evidence_backed',
178
+ status: 'done',
179
+ actor: 'alpha',
180
+ branch: branchName,
181
+ sessionId: 'session_alpha',
182
+ commandId: 'cmd_task',
183
+ correlationId: 'task_evidence_backed',
184
+ sourceTool: 'update_task',
185
+ evidence: {
186
+ summary: 'Completed the task with canonical evidence',
187
+ verification: 'Validated task projection and event references',
188
+ files_changed: ['docs/architecture/runtime-contract.md'],
189
+ confidence: 93,
190
+ },
191
+ });
192
+ assert(taskCompletion.success, 'updateTaskStatus() should succeed for evidence-backed task completion.', problems);
193
+ assert(taskCompletion.evidence_ref && taskCompletion.evidence_ref.evidence_id, 'Task completion must return an evidence reference.', problems);
194
+
195
+ const evidenceFile = branchPaths.getEvidenceFile(branchName);
196
+ const mainEvidenceFile = branchPaths.getEvidenceFile('main');
197
+ const evidenceStore = readJson(evidenceFile);
198
+ const workflows = readJson(branchPaths.getWorkflowsFile(branchName));
199
+ const tasks = readJson(branchPaths.getTasksFile(branchName));
200
+ const branchEvents = eventLog.readBranchEvents(branchName);
201
+ const branchEventTypes = branchEvents.map((event) => event.type);
202
+ const verifyWorkflow = workflows.find((workflow) => workflow.id === 'wf_verify_path');
203
+ const manualWorkflow = workflows.find((workflow) => workflow.id === 'wf_manual_advance');
204
+ const completedTask = tasks.find((task) => task.id === 'task_evidence_backed');
205
+
206
+ assert(fs.existsSync(evidenceFile), 'Evidence-backed completions must materialize a branch-scoped evidence file.', problems);
207
+ assert(!fs.existsSync(mainEvidenceFile), 'Evidence recorded on a feature branch must not leak into the main-branch evidence file.', problems);
208
+ assert(Array.isArray(evidenceStore.records) && evidenceStore.records.length === 3, 'Evidence store should contain one record for verify_and_advance, one for advance_workflow, and one for task completion.', problems);
209
+ assert(evidenceStore.records.every((record) => record.recorded_by_session === 'session_alpha'), 'Evidence records must retain the originating session id.', problems);
210
+ assert(evidenceStore.records.some((record) => record.source_tool === 'verify_and_advance'), 'Evidence store must retain verify_and_advance provenance.', problems);
211
+ assert(evidenceStore.records.some((record) => record.source_tool === 'advance_workflow'), 'Evidence store must retain advance_workflow provenance.', problems);
212
+ assert(evidenceStore.records.some((record) => record.source_tool === 'update_task'), 'Evidence store must retain update_task provenance.', problems);
213
+
214
+ assert(verifyWorkflow.steps[0].status === 'done', 'Verified workflow step should be marked done.', problems);
215
+ assert(verifyWorkflow.steps[0].evidence_ref && verifyWorkflow.steps[0].evidence_ref.evidence_id === verifyAdvance.evidence_ref.evidence_id, 'Verified workflow step must reference its evidence record.', problems);
216
+ assert(verifyWorkflow.steps[1].status === 'in_progress', 'Verified workflow advancement should start the next pending step.', problems);
217
+ assert(manualWorkflow.steps[0].evidence_ref && manualWorkflow.steps[0].evidence_ref.evidence_id === manualAdvance.evidence_ref.evidence_id, 'Manual workflow advancement must reference its evidence record.', problems);
218
+ assert(completedTask.status === 'done', 'Evidence-backed task completion must mark the task done.', problems);
219
+ assert(completedTask.evidence_ref && completedTask.evidence_ref.evidence_id === taskCompletion.evidence_ref.evidence_id, 'Completed task must reference its evidence record.', problems);
220
+
221
+ assert(branchEventTypes.filter((type) => type === 'evidence.recorded').length === 3, 'Canonical branch events must record three evidence.recorded events for the successful targeted paths.', problems);
222
+ assert(branchEventTypes.includes('workflow.step_completed'), 'Canonical branch events must include workflow.step_completed for evidence-backed workflow advancement.', problems);
223
+ assert(branchEventTypes.includes('task.completed'), 'Canonical branch events must include task.completed for evidence-backed task completion.', problems);
224
+ } finally {
225
+ fs.rmSync(tempRoot, { recursive: true, force: true });
226
+ }
227
+
228
+ if (problems.length > 0) {
229
+ fail(['Evidence-backed completion validation failed.', ...problems.map((problem) => `- ${problem}`)], 1);
230
+ }
231
+
232
+ console.log([
233
+ 'Evidence-backed completion validation passed.',
234
+ 'Validated canonical evidence storage by reference for verify_and_advance, advance_workflow, and terminal update_task flows.',
235
+ 'Validated evidence-less terminal transitions fail closed and workflow handoffs keep evidence/session linkage in the server routing layer.',
236
+ ].join('\n'));
237
+ }
238
+
239
+ main();