solmate-skills 2.0.11 → 2.0.13

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 (36) hide show
  1. package/AGENTS.md +11 -0
  2. package/CLAUDE.md +13 -3
  3. package/README.md +135 -286
  4. package/USAGE.md +893 -0
  5. package/bin/cli.js +198 -3
  6. package/bin/harness-artifact.js +621 -0
  7. package/bin/harness-artifact.test.js +520 -0
  8. package/bin/harness-check.js +373 -0
  9. package/bin/harness-check.test.js +159 -0
  10. package/bin/test.js +2 -0
  11. package/docs/01_Concept_Design/01_AGENT_HARNESS_REQUIREMENTS_ANALYSIS.md +162 -0
  12. package/docs/03_Technical_Specs/01_AGENT_HARNESS_ARCHITECTURE.md +376 -0
  13. package/docs/03_Technical_Specs/assets/01_agent_harness_data_flow.svg +94 -0
  14. package/docs/04_Logic_Progress/00_BACKLOG.md +339 -0
  15. package/docs/04_Logic_Progress/01_EXECUTION_PLAN.md +160 -0
  16. package/docs/04_Logic_Progress/03_DECISION_LOG.md +98 -0
  17. package/docs/05_QA_Validation/01_TEST_SCENARIOS.md +313 -0
  18. package/docs/05_QA_Validation/02_AGENT_HARNESS_DESIGN_REVIEW.md +100 -0
  19. package/docs/05_QA_Validation/03_AGENT_HARNESS_CONTRACT_QA.md +96 -0
  20. package/manage-collaboration/SKILL.md +2 -0
  21. package/package.json +6 -4
  22. package/rules-docs/SKILL.md +15 -1
  23. package/rules-product/SKILL.md +37 -6
  24. package/rules-product/agents/openai.yaml +2 -2
  25. package/rules-workflow/SKILL.md +38 -6
  26. package/rules-workflow/adapters/claude/solmate-context-reader.md +17 -0
  27. package/rules-workflow/adapters/claude/solmate-implementer.md +20 -0
  28. package/rules-workflow/adapters/claude/solmate-verifier.md +21 -0
  29. package/rules-workflow/agents/openai.yaml +2 -2
  30. package/rules-workflow/resources/agent-harness-contract.md +187 -0
  31. package/rules-workflow/resources/agent-harness-v1.schema.json +432 -0
  32. package/verify-docs/SKILL.md +33 -6
  33. package/verify-docs/agents/openai.yaml +2 -2
  34. package/verify-implementation/SKILL.md +21 -2
  35. package/verify-implementation/agents/openai.yaml +2 -2
  36. package/verify-skills/SKILL.md +34 -1
@@ -0,0 +1,520 @@
1
+ const assert = require('assert');
2
+ const childProcess = require('child_process');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const {
7
+ loadContract,
8
+ ownershipScopesOverlap,
9
+ validateEvent,
10
+ validateEvents,
11
+ validateManifest,
12
+ validateMessage,
13
+ } = require('./harness-artifact');
14
+
15
+ const cliPath = path.join(__dirname, 'cli.js');
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'solmate-harness-artifact-'));
17
+ const workspace = path.join(root, '_workspace', 'harness', 'TASK-HARNESS-002');
18
+ fs.mkdirSync(workspace, { recursive: true });
19
+
20
+ const validManifest = {
21
+ schema_version: 1,
22
+ artifact_type: 'task_manifest',
23
+ task_id: 'TASK-HARNESS-002',
24
+ work_type: 'code',
25
+ current_state: 'COMPLETE',
26
+ active_attempt: 1,
27
+ topology: {
28
+ pattern: 'LINEAR',
29
+ reason: 'The contract change has one implementation owner.',
30
+ agent_count: 4,
31
+ fallback: 'Run the same roles sequentially.',
32
+ },
33
+ roles: [
34
+ { role_id: 'coordinator', activation: 'ACTIVE', reason: 'Owns task state and user decisions.' },
35
+ { role_id: 'context-reader', activation: 'ACTIVE', reason: 'Reads every linked reference.' },
36
+ { role_id: 'implementer', activation: 'ACTIVE', reason: 'Owns the declared source changes.' },
37
+ { role_id: 'qa-inspector', activation: 'ACTIVE', reason: 'Independently verifies the change.' },
38
+ { role_id: 'architect', activation: 'SKIPPED', reason: 'The approved architecture is already canonical.' },
39
+ ],
40
+ write_ownership: [
41
+ { role_id: 'implementer', paths: ['bin/**', 'rules-workflow/resources/**'], mode: 'EXCLUSIVE' },
42
+ ],
43
+ canonical_documents: [
44
+ {
45
+ kind: 'REQUIREMENTS',
46
+ path: 'docs/01_Concept_Design/01_AGENT_HARNESS_REQUIREMENTS_ANALYSIS.md',
47
+ },
48
+ {
49
+ kind: 'ARCHITECTURE',
50
+ path: 'docs/03_Technical_Specs/01_AGENT_HARNESS_ARCHITECTURE.md',
51
+ },
52
+ {
53
+ kind: 'QA',
54
+ path: 'docs/05_QA_Validation/01_TEST_SCENARIOS.md',
55
+ },
56
+ ],
57
+ receipts: [
58
+ {
59
+ type: 'REQUIREMENTS',
60
+ status: 'PASS',
61
+ artifact_ref: 'docs/01_Concept_Design/01_AGENT_HARNESS_REQUIREMENTS_ANALYSIS.md',
62
+ },
63
+ {
64
+ type: 'CONTEXT',
65
+ status: 'PASS',
66
+ artifact_ref: 'docs/04_Logic_Progress/00_BACKLOG.md',
67
+ },
68
+ {
69
+ type: 'DESIGN',
70
+ status: 'SKIPPED',
71
+ artifact_ref: 'docs/04_Logic_Progress/00_BACKLOG.md',
72
+ reason: 'The approved architecture already covers this implementation.',
73
+ },
74
+ {
75
+ type: 'CHANGE',
76
+ status: 'PASS',
77
+ artifact_ref: 'docs/04_Logic_Progress/00_BACKLOG.md',
78
+ },
79
+ {
80
+ type: 'VERIFICATION',
81
+ status: 'PASS',
82
+ artifact_ref: 'docs/05_QA_Validation/03_AGENT_HARNESS_CONTRACT_QA.md',
83
+ },
84
+ ],
85
+ };
86
+
87
+ const validMessage = {
88
+ schema_version: 1,
89
+ artifact_type: 'agent_message',
90
+ message_id: 'msg-task-harness-002-01',
91
+ task_id: 'TASK-HARNESS-002',
92
+ attempt: 1,
93
+ timestamp: '2026-07-17T01:31:00+09:00',
94
+ from: 'implementer',
95
+ to: ['coordinator'],
96
+ type: 'HANDOFF',
97
+ status: 'PASS',
98
+ summary: 'The versioned contract validator is ready for independent QA.',
99
+ requirement_refs: ['FR-006', 'FR-007'],
100
+ artifact_refs: ['bin/harness-artifact.js'],
101
+ decision_refs: ['DEC-002'],
102
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
103
+ blockers: [],
104
+ next_action: 'Coordinator may start independent verification.',
105
+ retry_count: 0,
106
+ };
107
+
108
+ function event(sequence, from, to, fields = {}) {
109
+ return {
110
+ schema_version: 1,
111
+ artifact_type: 'state_event',
112
+ event_id: `evt-task-harness-002-${sequence}`,
113
+ task_id: 'TASK-HARNESS-002',
114
+ attempt: 1,
115
+ sequence,
116
+ timestamp: `2026-07-17T01:${String(31 + sequence).padStart(2, '0')}:00+09:00`,
117
+ actor: 'coordinator',
118
+ from,
119
+ to,
120
+ reason: `Advance the approved task to ${to}.`,
121
+ artifact_refs: [],
122
+ evidence_refs: [],
123
+ ...fields,
124
+ };
125
+ }
126
+
127
+ const validEvents = [
128
+ event(1, 'INTAKE', 'REQUIREMENTS_READY', {
129
+ artifact_refs: ['docs/01_Concept_Design/01_AGENT_HARNESS_REQUIREMENTS_ANALYSIS.md'],
130
+ }),
131
+ event(2, 'REQUIREMENTS_READY', 'CONTEXT_LOCKED', {
132
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
133
+ }),
134
+ event(3, 'CONTEXT_LOCKED', 'DESIGN_READY', {
135
+ artifact_refs: ['docs/03_Technical_Specs/01_AGENT_HARNESS_ARCHITECTURE.md'],
136
+ }),
137
+ event(4, 'DESIGN_READY', 'IMPLEMENTING', {
138
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
139
+ }),
140
+ event(5, 'IMPLEMENTING', 'CHANGE_READY', {
141
+ artifact_refs: ['bin/harness-artifact.js'],
142
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
143
+ }),
144
+ event(6, 'CHANGE_READY', 'VERIFYING', {
145
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
146
+ }),
147
+ event(7, 'VERIFYING', 'COMPLETE', {
148
+ evidence_refs: ['docs/05_QA_Validation/03_AGENT_HARNESS_CONTRACT_QA.md'],
149
+ }),
150
+ ];
151
+
152
+ assert.deepStrictEqual(validateManifest(validManifest), []);
153
+ assert.deepStrictEqual(validateMessage(validMessage, validManifest), []);
154
+ assert.deepStrictEqual(validateEvents(validEvents, validManifest), []);
155
+ assert.strictEqual(ownershipScopesOverlap('bin/**', 'bin/cli.js'), true);
156
+ assert.strictEqual(ownershipScopesOverlap('bin/**', 'docs/00.md'), false);
157
+ assert.strictEqual(ownershipScopesOverlap('bin/cli.js', 'bin/./cli.js'), true);
158
+ assert.strictEqual(ownershipScopesOverlap('a//b', 'a/b'), true);
159
+ assert.strictEqual(ownershipScopesOverlap('./**', 'bin/cli.js'), true);
160
+ assert.strictEqual(loadContract().oneOf.length, 3);
161
+
162
+ for (const field of Object.keys(validManifest)) {
163
+ const invalid = { ...validManifest, [field]: null };
164
+ assert.doesNotThrow(() => validateManifest(invalid), `manifest field ${field} must not throw`);
165
+ }
166
+ for (const field of Object.keys(validMessage)) {
167
+ const invalid = { ...validMessage, [field]: null };
168
+ assert.doesNotThrow(
169
+ () => validateMessage(invalid, validManifest),
170
+ `message field ${field} must not throw`,
171
+ );
172
+ }
173
+ for (const field of Object.keys(validEvents[0])) {
174
+ const invalid = { ...validEvents[0], [field]: null };
175
+ assert.doesNotThrow(() => validateEvent(invalid), `event field ${field} must not throw`);
176
+ assert.doesNotThrow(
177
+ () => validateEvents([invalid], validManifest),
178
+ `event log field ${field} must not throw`,
179
+ );
180
+ }
181
+
182
+ const unsupportedVersion = { ...validManifest, schema_version: 2 };
183
+ assert(validateManifest(unsupportedVersion)
184
+ .some(error => error.includes('$.schema_version must equal 1')));
185
+
186
+ const prototypeNamedFields = JSON.parse(
187
+ `${JSON.stringify(validManifest).slice(0, -1)},"constructor":"forbidden","__proto__":"forbidden"}`,
188
+ );
189
+ const prototypeFieldErrors = validateManifest(prototypeNamedFields);
190
+ assert(prototypeFieldErrors.some(error => error.includes('$.constructor is not allowed')));
191
+ assert(prototypeFieldErrors.some(error => error.includes('$.__proto__ is not allowed')));
192
+
193
+ const missingVerification = JSON.parse(JSON.stringify(validManifest));
194
+ missingVerification.receipts = missingVerification.receipts
195
+ .filter(receipt => receipt.type !== 'VERIFICATION');
196
+ assert(validateManifest(missingVerification)
197
+ .some(error => error.includes('COMPLETE requires VERIFICATION receipt evidence')));
198
+
199
+ const overlappingOwnership = JSON.parse(JSON.stringify(validManifest));
200
+ overlappingOwnership.roles.push({
201
+ role_id: 'release-guardian',
202
+ activation: 'ACTIVE',
203
+ reason: 'Checks release evidence.',
204
+ });
205
+ overlappingOwnership.topology.agent_count = 5;
206
+ overlappingOwnership.write_ownership.push({
207
+ role_id: 'release-guardian',
208
+ paths: ['bin/cli.js'],
209
+ mode: 'EXCLUSIVE',
210
+ });
211
+ assert(validateManifest(overlappingOwnership)
212
+ .some(error => error.includes('overlaps bin/** owned by implementer')));
213
+
214
+ const readOnlyOwnership = JSON.parse(JSON.stringify(validManifest));
215
+ readOnlyOwnership.write_ownership.push({
216
+ role_id: 'context-reader',
217
+ paths: ['docs/**'],
218
+ mode: 'EXCLUSIVE',
219
+ });
220
+ assert(validateManifest(readOnlyOwnership)
221
+ .some(error => error.includes('Read-only role context-reader')));
222
+
223
+ const ambiguousOwnership = JSON.parse(JSON.stringify(validManifest));
224
+ ambiguousOwnership.write_ownership[0].paths = ['bin/*'];
225
+ assert(validateManifest(ambiguousOwnership)
226
+ .some(error => error.includes('safe project-relative path')));
227
+
228
+ for (const recursiveAlias of ['bin//**', './/**']) {
229
+ const invalidRecursiveScope = JSON.parse(JSON.stringify(validManifest));
230
+ invalidRecursiveScope.write_ownership[0].paths = [recursiveAlias];
231
+ assert(validateManifest(invalidRecursiveScope)
232
+ .some(error => error.includes('safe project-relative path')));
233
+ }
234
+
235
+ const aliasOwnership = JSON.parse(JSON.stringify(validManifest));
236
+ aliasOwnership.roles.push({
237
+ role_id: 'release-guardian',
238
+ activation: 'ACTIVE',
239
+ reason: 'Checks release evidence.',
240
+ });
241
+ aliasOwnership.topology.agent_count = 5;
242
+ aliasOwnership.write_ownership.push({
243
+ role_id: 'release-guardian',
244
+ paths: ['bin/./cli.js'],
245
+ mode: 'EXCLUSIVE',
246
+ });
247
+ const aliasErrors = validateManifest(aliasOwnership);
248
+ assert(aliasErrors.some(error => error.includes('safe project-relative path')));
249
+ assert(aliasErrors.some(error => error.includes('overlaps bin/** owned by implementer')));
250
+
251
+ const invalidOwnershipType = JSON.parse(JSON.stringify(validManifest));
252
+ invalidOwnershipType.write_ownership.push({
253
+ role_id: 'implementer',
254
+ paths: [42],
255
+ mode: 'EXCLUSIVE',
256
+ });
257
+ assert(validateManifest(invalidOwnershipType)
258
+ .some(error => error.includes('must be string')));
259
+
260
+ const inactiveRecipient = { ...validMessage, to: ['architect'] };
261
+ assert(validateMessage(inactiveRecipient, validManifest)
262
+ .some(error => error.includes('architect is not ACTIVE')));
263
+
264
+ const validPeerQuestion = {
265
+ ...validMessage,
266
+ message_id: 'msg-task-harness-002-peer-question',
267
+ to: ['qa-inspector'],
268
+ type: 'QUESTION',
269
+ status: 'PENDING',
270
+ artifact_refs: [],
271
+ evidence_refs: [],
272
+ next_action: 'QA Inspector may answer the bounded clarification.',
273
+ };
274
+ assert.deepStrictEqual(validateMessage(validPeerQuestion, validManifest), []);
275
+
276
+ const peerPass = {
277
+ ...validPeerQuestion,
278
+ message_id: 'msg-task-harness-002-peer-pass',
279
+ type: 'STATUS',
280
+ status: 'PASS',
281
+ next_action: 'Treat the implementation as complete.',
282
+ };
283
+ assert(validateMessage(peerPass, validManifest)
284
+ .some(error => error.includes('Direct peer messages may only use INFO or PENDING status')));
285
+
286
+ const invalidCalendarTimestamp = {
287
+ ...validMessage,
288
+ timestamp: '2026-02-30T12:00:00Z',
289
+ };
290
+ assert(validateMessage(invalidCalendarTimestamp, validManifest)
291
+ .some(error => error.includes('Solmate canonical date-time profile')));
292
+
293
+ const validLeapDayTimestamp = {
294
+ ...validMessage,
295
+ timestamp: '2024-02-29T23:59:59.123+09:00',
296
+ };
297
+ assert.deepStrictEqual(validateMessage(validLeapDayTimestamp, validManifest), []);
298
+
299
+ const validLeapSecondTimestamp = {
300
+ ...validMessage,
301
+ timestamp: '2016-12-31T23:59:60Z',
302
+ };
303
+ assert(validateMessage(validLeapSecondTimestamp, validManifest)
304
+ .some(error => error.includes('Solmate canonical date-time profile')));
305
+
306
+ const invalidSecondTimestamp = {
307
+ ...validMessage,
308
+ timestamp: '2016-12-31T23:59:61Z',
309
+ };
310
+ assert(validateMessage(invalidSecondTimestamp, validManifest)
311
+ .some(error => error.includes('Solmate canonical date-time profile')));
312
+
313
+ for (const timestamp of ['2026-07-17t12:34:56z', '0000-01-01T00:00:00Z']) {
314
+ assert(validateMessage({ ...validMessage, timestamp }, validManifest)
315
+ .some(error => error.includes('Solmate canonical date-time profile')));
316
+ }
317
+
318
+ const illegalTransition = event(1, 'CHANGE_READY', 'COMPLETE', {
319
+ evidence_refs: ['docs/05_QA_Validation/03_AGENT_HARNESS_CONTRACT_QA.md'],
320
+ });
321
+ assert(validateEvent(illegalTransition)
322
+ .some(error => error.includes('Illegal state transition')));
323
+
324
+ const unauthorizedTransition = {
325
+ ...validEvents[6],
326
+ actor: 'implementer',
327
+ };
328
+ assert(validateEvent(unauthorizedTransition)
329
+ .some(error => error.includes('Only coordinator')));
330
+
331
+ const missingEvidence = {
332
+ ...validEvents[6],
333
+ evidence_refs: [],
334
+ };
335
+ assert(validateEvent(missingEvidence)
336
+ .some(error => error.includes('requires evidence_refs')));
337
+
338
+ const duplicateEventIds = validEvents.map(value => ({ ...value }));
339
+ duplicateEventIds[1].event_id = duplicateEventIds[0].event_id;
340
+ assert(validateEvents(duplicateEventIds, validManifest)
341
+ .some(error => error.includes('event_id must be unique')));
342
+
343
+ const cancelledReworkEvents = [
344
+ ...validEvents.slice(0, 4),
345
+ event(5, 'IMPLEMENTING', 'REWORK'),
346
+ event(6, 'REWORK', 'CANCELLED'),
347
+ ];
348
+ assert.deepStrictEqual(validateEvents(cancelledReworkEvents), []);
349
+
350
+ const restartedReworkEvents = [
351
+ ...validEvents.slice(0, 4),
352
+ event(5, 'IMPLEMENTING', 'REWORK'),
353
+ {
354
+ ...event(6, 'REWORK', 'IMPLEMENTING', {
355
+ evidence_refs: ['docs/04_Logic_Progress/00_BACKLOG.md'],
356
+ }),
357
+ attempt: 2,
358
+ },
359
+ ];
360
+ assert.deepStrictEqual(validateEvents(restartedReworkEvents), []);
361
+
362
+ const prematureAttemptEvents = cancelledReworkEvents.map(value => ({ ...value }));
363
+ prematureAttemptEvents[4].attempt = 2;
364
+ assert(validateEvents(prematureAttemptEvents)
365
+ .some(error => error.includes('attempt must be 1')));
366
+
367
+ const blockedDecisionEvents = [
368
+ validEvents[0],
369
+ event(2, 'REQUIREMENTS_READY', 'BLOCKED_DECISION', {
370
+ resume_state: 'REQUIREMENTS_READY',
371
+ }),
372
+ event(3, 'BLOCKED_DECISION', 'REQUIREMENTS_READY', {
373
+ artifact_refs: ['docs/01_Concept_Design/01_AGENT_HARNESS_REQUIREMENTS_ANALYSIS.md'],
374
+ }),
375
+ ];
376
+ assert.deepStrictEqual(validateEvents(blockedDecisionEvents), []);
377
+
378
+ const invalidResumeEvents = blockedDecisionEvents.map(value => ({ ...value }));
379
+ invalidResumeEvents[2].to = 'CONTEXT_LOCKED';
380
+ invalidResumeEvents[2].evidence_refs = ['docs/04_Logic_Progress/00_BACKLOG.md'];
381
+ assert(validateEvents(invalidResumeEvents)
382
+ .some(error => error.includes('must resume to REQUIREMENTS_READY')));
383
+
384
+ const invalidSequenceEvents = validEvents.map(value => ({ ...value }));
385
+ invalidSequenceEvents[1].sequence = 9;
386
+ assert(validateEvents(invalidSequenceEvents, validManifest)
387
+ .some(error => error.includes('sequence must equal 2')));
388
+
389
+ const invalidAttemptEvents = validEvents.map(value => ({ ...value }));
390
+ invalidAttemptEvents[1].attempt = 3;
391
+ assert(validateEvents(invalidAttemptEvents, validManifest)
392
+ .some(error => error.includes('attempt must be 1')));
393
+
394
+ const mismatchedStateManifest = { ...validManifest, current_state: 'VERIFYING' };
395
+ assert(validateEvents(validEvents, mismatchedStateManifest)
396
+ .some(error => error.includes('Manifest current_state must match')));
397
+
398
+ const manifestPath = path.join(workspace, 'manifest.json');
399
+ const messagePath = path.join(workspace, 'message.json');
400
+ const eventsPath = path.join(workspace, 'events.jsonl');
401
+ fs.writeFileSync(manifestPath, `${JSON.stringify(validManifest, null, 2)}\n`);
402
+ fs.writeFileSync(messagePath, `${JSON.stringify(validMessage, null, 2)}\n`);
403
+ fs.writeFileSync(eventsPath, `${validEvents.map(value => JSON.stringify(value)).join('\n')}\n`);
404
+
405
+ for (const [artifactType, artifactPath] of [
406
+ ['manifest', manifestPath],
407
+ ['message', messagePath],
408
+ ['events', eventsPath],
409
+ ]) {
410
+ const args = [cliPath, 'validate-harness', artifactType, artifactPath, '--strict'];
411
+ if (artifactType !== 'manifest') args.push('--manifest', manifestPath);
412
+ const result = childProcess.spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8' });
413
+ assert.strictEqual(result.status, 0, result.stderr || result.stdout);
414
+ assert(result.stdout.includes(`Harness artifact ${artifactType}: PASS`));
415
+ }
416
+
417
+ const invalidMessage = { ...validMessage };
418
+ delete invalidMessage.summary;
419
+ const invalidMessagePath = path.join(workspace, 'invalid-message.json');
420
+ fs.writeFileSync(invalidMessagePath, `${JSON.stringify(invalidMessage, null, 2)}\n`);
421
+
422
+ const warningResult = childProcess.spawnSync(
423
+ process.execPath,
424
+ [cliPath, 'validate-harness', 'message', invalidMessagePath, '--manifest', manifestPath],
425
+ { cwd: root, encoding: 'utf8' },
426
+ );
427
+ assert.strictEqual(warningResult.status, 0, warningResult.stderr || warningResult.stdout);
428
+ assert(warningResult.stdout.includes('Harness artifact message: WARN'));
429
+
430
+ const blockingResult = childProcess.spawnSync(
431
+ process.execPath,
432
+ [cliPath, 'validate-harness', 'message', invalidMessagePath, '--manifest', manifestPath, '--strict'],
433
+ { cwd: root, encoding: 'utf8' },
434
+ );
435
+ assert.strictEqual(blockingResult.status, 1, blockingResult.stderr || blockingResult.stdout);
436
+ assert(blockingResult.stdout.includes('Harness artifact message: BLOCK'));
437
+
438
+ const jsonBlockingResult = childProcess.spawnSync(
439
+ process.execPath,
440
+ [
441
+ cliPath,
442
+ 'validate-harness',
443
+ 'message',
444
+ invalidMessagePath,
445
+ '--manifest',
446
+ manifestPath,
447
+ '--mode',
448
+ 'blocking',
449
+ '--json',
450
+ ],
451
+ { cwd: root, encoding: 'utf8' },
452
+ );
453
+ assert.strictEqual(jsonBlockingResult.status, 1, jsonBlockingResult.stderr || jsonBlockingResult.stdout);
454
+ assert.strictEqual(JSON.parse(jsonBlockingResult.stdout).valid, false);
455
+
456
+ const missingManifestResult = childProcess.spawnSync(
457
+ process.execPath,
458
+ [cliPath, 'validate-harness', 'message', messagePath, '--strict'],
459
+ { cwd: root, encoding: 'utf8' },
460
+ );
461
+ assert.strictEqual(missingManifestResult.status, 2, missingManifestResult.stderr || missingManifestResult.stdout);
462
+ assert(missingManifestResult.stdout.includes('--manifest is required'));
463
+
464
+ for (const [name, invalidEvent] of [
465
+ ['illegal-transition', illegalTransition],
466
+ ['unauthorized-transition', unauthorizedTransition],
467
+ ['missing-evidence', missingEvidence],
468
+ ]) {
469
+ const invalidEventsPath = path.join(workspace, `${name}.jsonl`);
470
+ fs.writeFileSync(invalidEventsPath, `${JSON.stringify(invalidEvent)}\n`);
471
+ const result = childProcess.spawnSync(
472
+ process.execPath,
473
+ [cliPath, 'validate-harness', 'events', invalidEventsPath, '--manifest', manifestPath, '--strict'],
474
+ { cwd: root, encoding: 'utf8' },
475
+ );
476
+ assert.strictEqual(result.status, 1, result.stderr || result.stdout);
477
+ assert(result.stdout.includes('Harness artifact events: BLOCK'));
478
+ }
479
+
480
+ const malformedPath = path.join(workspace, 'malformed.json');
481
+ fs.writeFileSync(malformedPath, '{ invalid json\n');
482
+ const malformedResult = childProcess.spawnSync(
483
+ process.execPath,
484
+ [cliPath, 'validate-harness', 'manifest', malformedPath, '--strict'],
485
+ { cwd: root, encoding: 'utf8' },
486
+ );
487
+ assert.strictEqual(malformedResult.status, 2, malformedResult.stderr || malformedResult.stdout);
488
+ assert(malformedResult.stdout.includes('Harness artifact check: ERROR'));
489
+
490
+ const malformedEventsPath = path.join(workspace, 'malformed-events.jsonl');
491
+ fs.writeFileSync(malformedEventsPath, `${JSON.stringify(validEvents[0])}\n{ invalid json\n`);
492
+ const malformedEventsResult = childProcess.spawnSync(
493
+ process.execPath,
494
+ [cliPath, 'validate-harness', 'events', malformedEventsPath, '--manifest', manifestPath, '--strict'],
495
+ { cwd: root, encoding: 'utf8' },
496
+ );
497
+ assert.strictEqual(malformedEventsResult.status, 2, malformedEventsResult.stderr || malformedEventsResult.stdout);
498
+ assert(malformedEventsResult.stdout.includes('Invalid JSON on line 2'));
499
+
500
+ const nullEventsPath = path.join(workspace, 'null-events.jsonl');
501
+ fs.writeFileSync(nullEventsPath, 'null\n');
502
+ const nullEventsResult = childProcess.spawnSync(
503
+ process.execPath,
504
+ [cliPath, 'validate-harness', 'events', nullEventsPath, '--manifest', manifestPath, '--strict'],
505
+ { cwd: root, encoding: 'utf8' },
506
+ );
507
+ assert.strictEqual(nullEventsResult.status, 1, nullEventsResult.stderr || nullEventsResult.stdout);
508
+ assert(nullEventsResult.stdout.includes('Harness artifact events: BLOCK'));
509
+
510
+ const invalidOwnershipPath = path.join(workspace, 'invalid-ownership.json');
511
+ fs.writeFileSync(invalidOwnershipPath, `${JSON.stringify(invalidOwnershipType, null, 2)}\n`);
512
+ const invalidOwnershipResult = childProcess.spawnSync(
513
+ process.execPath,
514
+ [cliPath, 'validate-harness', 'manifest', invalidOwnershipPath, '--strict'],
515
+ { cwd: root, encoding: 'utf8' },
516
+ );
517
+ assert.strictEqual(invalidOwnershipResult.status, 1, invalidOwnershipResult.stderr || invalidOwnershipResult.stdout);
518
+ assert(invalidOwnershipResult.stdout.includes('Harness artifact manifest: BLOCK'));
519
+
520
+ console.log('harness artifact checks ok');