harness-everything 0.40.0 → 0.41.1
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.
- package/.agents/harness-everything/project-docs.json +9 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +51 -0
- package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/2-injected-architecture.md +229 -0
- package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stdout.json +33 -0
- package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stdout.json +33 -0
- package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/README.md +54 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-injected-document.md +328 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stdout.json +127 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stdout.json +127 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.input.md +8 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.stdout.json +16 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/README.md +34 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/after.json +248 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/before.json +254 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/probe.js +26 -0
- package/benchmarks/results/f2-doc-craft-baseline/README.md +32 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1-report.md +84 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1-task.md +21 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1.diff +19 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2-report.md +110 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2-task.md +21 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2.diff +19 -0
- package/benchmarks/results/f3-derived-views-baseline/README.md +34 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2-report.md +43 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2-task.md +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2.diff +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3-report.md +57 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3-task.md +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3.diff +21 -0
- package/benchmarks/results/live-host/claude-325-3b-s2b/README.md +99 -0
- package/benchmarks/results/live-host/claude-325-3b-s2b/capture.sanitized.jsonl +26 -0
- package/ci/archive/325-prep/README.md +71 -0
- package/ci/{prep → archive/325-prep}/doc-reconciliation-red.js +9 -7
- package/ci/archive/325-prep/f4-expected-red.json +34 -0
- package/ci/archive/325-prep/historical-qualification.test.js +143 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +17 -4
- package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +8 -56
- package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +2 -2
- package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +17 -5
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/harness.js +415 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/off-handoff-golden.json +16 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-failure.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success-minimal.json +9 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success.json +13 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-start.json +7 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-freetext-reject.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-reject.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-task-result.json +19 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-capture-only.json +99 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-legacy.json +67 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-concurrent-writers.json +62 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-lookalikes-runtime-mutations.json +67 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-malformed.json +85 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-normal-session.json +74 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-runtime-drift-later-npm-test.json +58 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-valid-direct-quoted-review.json +85 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/f4/cases.json +2267 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/cases.json +1110 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/legacy-off-golden.json +674 -0
- package/ci/lib/doc-reconciliation-file-spy.js +23 -0
- package/ci/lib/doc-reconciliation-prep-fixtures.js +136 -10
- package/ci/lib/doc-reconciliation-qualification-preload.js +37 -0
- package/ci/lib/doc-reconciliation-replay.js +104 -0
- package/ci/lib/doc-reconciliation-staged-tree.js +102 -0
- package/ci/mechanism-2p-opencode-v2-entrypoint.test.js +179 -0
- package/ci/mechanism-325-3b-preparation.test.js +27 -101
- package/ci/mechanism-325-3b-s2b-capture.test.js +561 -0
- package/ci/mechanism-325-3b-s2b-wiring.test.js +668 -0
- package/ci/mechanism-325-f1-doc-check.test.js +674 -0
- package/ci/mechanism-325-f2-doc-craft.test.js +597 -0
- package/ci/mechanism-325-f3-derived-views.test.js +798 -0
- package/ci/mechanism-325-f4-invariants.test.js +606 -0
- package/ci/mechanism-325-f4-projections.test.js +382 -0
- package/ci/mechanism-325-f4-qualification.test.js +228 -0
- package/ci/mechanism-325-f4-trust-boundary.test.js +880 -0
- package/ci/mechanism-365-step-edit-accounting.test.js +395 -0
- package/ci/mutation-check.js +26 -3
- package/docs/architecture.md +5 -0
- package/docs/doc-workflow-reconciliation.md +331 -4
- package/docs/document-lifecycle.md +11 -0
- package/docs/governed-index.md +20 -0
- package/docs/platform-capabilities.md +22 -2
- package/grill-with-docs/SKILL.md +1 -1
- package/grill-with-docs/references/session-playbook.md +4 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/references/doc-check.md +186 -0
- package/harness-everything/references/documentation/_shared/maintenance.md +41 -0
- package/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
- package/harness-everything/references/triage-and-tiers.md +4 -0
- package/harness-everything/scripts/doc-check.js +442 -0
- package/harness-everything/scripts/doc-lifecycle.js +3 -0
- package/hooks/hooks.json +46 -0
- package/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
- package/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
- package/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
- package/hooks/scripts/lib/workflow-obligations.js +7 -0
- package/hooks/scripts/state-persist.js +105 -3
- package/hooks/scripts/workflow-stop-gate.js +7 -2
- package/opencode-plugin/README.md +15 -0
- package/opencode-plugin/index.mjs +77 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +7 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +105 -3
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +7 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +1 -1
- package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +4 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/references/doc-check.md +186 -0
- package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/maintenance.md +41 -0
- package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +4 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +442 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +3 -0
- package/plugins/harness-everything/skills/repo-docs/SKILL.md +1 -1
- package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +4 -0
- package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-spec/references/process.md +4 -0
- package/plugins/harness-everything/skills/to-tickets/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +4 -0
- package/repo-docs/SKILL.md +1 -1
- package/repo-docs/references/process-guide.md +4 -0
- package/sbom.cdx.json +7 -7
- package/to-spec/SKILL.md +1 -1
- package/to-spec/references/process.md +4 -0
- package/to-tickets/SKILL.md +1 -1
- package/to-tickets/references/publishing-and-templates.md +4 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared harness for the R2c-1 S2b capture/origin-provider RED suite.
|
|
4
|
+
// Test-only fixture helper: simulated Claude hook payloads (see ./*.json), a
|
|
5
|
+
// two-leg module loader that reports missing S2b modules as structured FAIL
|
|
6
|
+
// instead of crashing, and a driver that runs the real state-persist.js script
|
|
7
|
+
// per leg with an isolated HARNESS_STATE_HOME.
|
|
8
|
+
// Nothing here is production code; nothing here grants origin, credit or
|
|
9
|
+
// trust. Fixtures simulate payloads; retained live evidence (doc section
|
|
10
|
+
// "Retained live-capture gate") is a different evidence layer.
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const HERE = __dirname;
|
|
19
|
+
const ROOT = path.resolve(HERE, '..', '..', '..', '..', '..');
|
|
20
|
+
// The retained live-capture evidence directory the doc names. It is a FUTURE
|
|
21
|
+
// artifact: no packet in this commit creates it, so the production-path
|
|
22
|
+
// qualification check must report unmet today.
|
|
23
|
+
const RETAINED_LIVE_DIR_REL = 'benchmarks/results/live-host/claude-325-3b-s2b';
|
|
24
|
+
|
|
25
|
+
const LEGS = [
|
|
26
|
+
{
|
|
27
|
+
name: 'source',
|
|
28
|
+
hooksScripts: path.join(ROOT, 'hooks', 'scripts'),
|
|
29
|
+
hooksManifest: path.join(ROOT, 'hooks', 'hooks.json'),
|
|
30
|
+
lib: path.join(ROOT, 'hooks', 'scripts', 'lib'),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'packaged',
|
|
34
|
+
hooksScripts: path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'scripts'),
|
|
35
|
+
hooksManifest: path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'hooks.json'),
|
|
36
|
+
lib: path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'scripts', 'lib'),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
class InfraError extends Error {}
|
|
41
|
+
|
|
42
|
+
// ---- Declared capture vocabulary (RED contract of this suite) --------------------
|
|
43
|
+
|
|
44
|
+
// Field names are the doc's own vocabulary (docs/doc-workflow-reconciliation.md
|
|
45
|
+
// S2b capture table): the event name, exact command, cwd, tool_use_id,
|
|
46
|
+
// session_id, prompt_id when supplied, agent_id/agent_type when supplied,
|
|
47
|
+
// duration_ms, success interrupted or failure is_interrupt, failure
|
|
48
|
+
// disposition, and a response digest/reference. Absent input fields stay
|
|
49
|
+
// absent in the retained record, never invented.
|
|
50
|
+
const CAPTURE_EVENT_NAMES = ['PostToolUse', 'PostToolUseFailure', 'SubagentStart', 'SubagentStop'];
|
|
51
|
+
const CAPTURE_BASE_REQUIRED = ['event', 'command', 'cwd', 'tool_use_id', 'session_id', 'responseDigest', 'responseRef'];
|
|
52
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
53
|
+
|
|
54
|
+
function stable(value) {
|
|
55
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(',')}]`;
|
|
56
|
+
if (value && typeof value === 'object') {
|
|
57
|
+
return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${stable(value[k])}`).join(',')}}`;
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sha256(text) {
|
|
63
|
+
// An absent value digests as the literal token "undefined" (stable(undefined) is not a string).
|
|
64
|
+
return crypto.createHash('sha256').update(String(typeof text === 'string' ? text : stable(text))).digest('hex');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function payload(name) {
|
|
68
|
+
return JSON.parse(fs.readFileSync(path.join(HERE, name), 'utf8'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function golden() {
|
|
72
|
+
return JSON.parse(fs.readFileSync(path.join(HERE, 'off-handoff-golden.json'), 'utf8'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---- Two-leg module loading (never crash on missing S2b modules) ----------------
|
|
76
|
+
|
|
77
|
+
const moduleCache = new Map();
|
|
78
|
+
function loadModule(leg, file) {
|
|
79
|
+
const key = `${leg.name}:${file}`;
|
|
80
|
+
if (moduleCache.has(key)) return moduleCache.get(key);
|
|
81
|
+
const abs = path.join(leg.lib, file);
|
|
82
|
+
const rel = path.relative(ROOT, abs);
|
|
83
|
+
let result;
|
|
84
|
+
if (!fs.existsSync(abs)) {
|
|
85
|
+
result = { error: `module-missing: ${rel} does not exist` };
|
|
86
|
+
} else {
|
|
87
|
+
try {
|
|
88
|
+
result = { mod: require(abs) };
|
|
89
|
+
} catch (error) {
|
|
90
|
+
result = { error: `load-error: ${rel}: ${String(error && error.message).split('\n')[0]}` };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
moduleCache.set(key, result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// requirements: [{ file, exports: [names] }]; fn(mods, leg) throws on failure.
|
|
98
|
+
// Missing module/exports surface as FAIL with the reason, never a crash.
|
|
99
|
+
function requirementsMet(leg, requirements) {
|
|
100
|
+
const mods = {};
|
|
101
|
+
for (const req of requirements || []) {
|
|
102
|
+
const loaded = loadModule(leg, req.file);
|
|
103
|
+
if (loaded.error) return { error: loaded.error };
|
|
104
|
+
for (const name of req.exports || []) {
|
|
105
|
+
if (typeof loaded.mod[name] !== 'function') {
|
|
106
|
+
return { error: `export-missing: ${req.file} does not export ${name}` };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
mods[req.file] = loaded.mod;
|
|
110
|
+
}
|
|
111
|
+
return { mods };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createRunner(label) {
|
|
115
|
+
const rows = [];
|
|
116
|
+
const counts = { PASS: 0, FAIL: 0, INFRA: 0 };
|
|
117
|
+
function emit(status, id, leg, detail) {
|
|
118
|
+
counts[status] += 1;
|
|
119
|
+
rows.push({ status, id, leg, detail: detail || '' });
|
|
120
|
+
console.log(` ${status} ${id}${leg ? ` [${leg}]` : ''}${detail ? ` — ${detail}` : ''}`);
|
|
121
|
+
}
|
|
122
|
+
function run(id, requirements, fn) {
|
|
123
|
+
for (const leg of LEGS) {
|
|
124
|
+
const met = requirementsMet(leg, requirements);
|
|
125
|
+
if (met.error) { emit('FAIL', id, leg.name, met.error); continue; }
|
|
126
|
+
try {
|
|
127
|
+
fn(met.mods, leg);
|
|
128
|
+
emit('PASS', id, leg.name);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error instanceof InfraError) emit('INFRA', id, leg.name, String(error && error.message).split('\n')[0]);
|
|
131
|
+
else emit('FAIL', id, leg.name, String(error && error.message).split('\n')[0]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function summary() {
|
|
136
|
+
console.log(`\n${label} summary: ${counts.PASS} PASS, ${counts.FAIL} FAIL, ${counts.INFRA} INFRA across ${rows.length} assertion-legs`);
|
|
137
|
+
return counts;
|
|
138
|
+
}
|
|
139
|
+
return { run, summary, counts, rows, emit };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- Real state-persist driver (one temp state home per drive) --------------------
|
|
143
|
+
|
|
144
|
+
function sessionDirectoryNameSafe(sessionId) {
|
|
145
|
+
return String(sessionId || 'default').replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^\.+/, '');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function snapshotSessionTree(stateHome, sessionId) {
|
|
149
|
+
const dirName = sessionDirectoryNameSafe(sessionId);
|
|
150
|
+
const hits = [];
|
|
151
|
+
(function walk(dir) {
|
|
152
|
+
if (!fs.existsSync(dir)) return;
|
|
153
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
154
|
+
const abs = path.join(dir, entry.name);
|
|
155
|
+
if (entry.isDirectory()) walk(abs);
|
|
156
|
+
else if (abs.split(path.sep).includes(dirName)) hits.push(abs);
|
|
157
|
+
}
|
|
158
|
+
})(stateHome);
|
|
159
|
+
const files = {};
|
|
160
|
+
for (const abs of hits) files[path.relative(stateHome, abs).replace(/\\/g, '/')] = fs.readFileSync(abs, 'utf8');
|
|
161
|
+
return files;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Deep-clones the fixture payload, binds cwd/workflow to this drive's temp
|
|
165
|
+
// workspace (state-persist resolution keys off cwd and HARNESS_WORKSPACE_ROOT),
|
|
166
|
+
// then runs the leg's state-persist.js with an isolated HARNESS_STATE_HOME.
|
|
167
|
+
// Returns { code, stdout, stderr, files } covering the session subtree only.
|
|
168
|
+
function driveStatePersist(leg, baseName, overrides = {}, options = {}) {
|
|
169
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'r2c1-drive-'));
|
|
170
|
+
const wsRoot = path.join(home, 'ws');
|
|
171
|
+
fs.mkdirSync(wsRoot, { recursive: true });
|
|
172
|
+
const stateHome = path.join(home, 'state-home');
|
|
173
|
+
fs.mkdirSync(stateHome, { recursive: true });
|
|
174
|
+
const driven = JSON.parse(JSON.stringify(payload(baseName)));
|
|
175
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
176
|
+
if (v === undefined) delete driven[k];
|
|
177
|
+
else driven[k] = v;
|
|
178
|
+
}
|
|
179
|
+
driven.cwd = wsRoot;
|
|
180
|
+
const env = {
|
|
181
|
+
...process.env,
|
|
182
|
+
CLAUDE: '1',
|
|
183
|
+
HARNESS_STATE_HOME: stateHome,
|
|
184
|
+
HARNESS_WORKSPACE_ROOT: wsRoot,
|
|
185
|
+
...(options.env || {}),
|
|
186
|
+
};
|
|
187
|
+
let result;
|
|
188
|
+
try {
|
|
189
|
+
result = spawnSync('node', [path.join(leg.hooksScripts, 'state-persist.js')], {
|
|
190
|
+
input: JSON.stringify(driven),
|
|
191
|
+
encoding: 'utf8',
|
|
192
|
+
env,
|
|
193
|
+
timeout: 30000,
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
throw new InfraError(`state-persist subprocess could not be started: ${error.message}`);
|
|
197
|
+
}
|
|
198
|
+
if (result.error) throw new InfraError(`state-persist subprocess error: ${String(result.error.message || result.error)}`);
|
|
199
|
+
const out = {
|
|
200
|
+
code: result.status,
|
|
201
|
+
stdout: result.stdout || '',
|
|
202
|
+
stderr: result.stderr || '',
|
|
203
|
+
files: snapshotSessionTree(stateHome, driven.session_id),
|
|
204
|
+
filesRoot: stateHome,
|
|
205
|
+
wsRoot,
|
|
206
|
+
// The payload actually sent to the hook (cwd rebound to wsRoot); capture records are compared to it.
|
|
207
|
+
payload: driven,
|
|
208
|
+
};
|
|
209
|
+
try { fs.rmSync(home, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- Capture-record discovery and shape validation --------------------------------
|
|
214
|
+
|
|
215
|
+
function deepScan(value, visit) {
|
|
216
|
+
if (value && typeof value === 'object') {
|
|
217
|
+
visit(value);
|
|
218
|
+
if (Array.isArray(value)) for (const item of value) deepScan(item, visit);
|
|
219
|
+
else for (const key of Object.keys(value)) deepScan(value[key], visit);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Finds any object inside any driven-file content that carries the full
|
|
224
|
+
// required capture vocabulary, whatever filename it was written to.
|
|
225
|
+
function findCaptureRecords(files) {
|
|
226
|
+
const found = [];
|
|
227
|
+
for (const content of Object.values(files)) {
|
|
228
|
+
let parsed;
|
|
229
|
+
try { parsed = JSON.parse(content); } catch { continue; }
|
|
230
|
+
deepScan(parsed, node => {
|
|
231
|
+
if (node && CAPTURE_EVENT_NAMES.includes(node.event)
|
|
232
|
+
&& CAPTURE_BASE_REQUIRED.every(k => Object.prototype.hasOwnProperty.call(node, k))) {
|
|
233
|
+
found.push(node);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return found;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Declared capture contract: doc field names; absent input fields stay absent;
|
|
241
|
+
// success records keep the success interrupted flag and no failure fields;
|
|
242
|
+
// failure records keep is_interrupt plus a nonempty failureDisposition.
|
|
243
|
+
function assertCaptureRecordShape(record, sourcePayload) {
|
|
244
|
+
const missing = CAPTURE_BASE_REQUIRED.filter(k => !(k in record));
|
|
245
|
+
if (missing.length) throw new Error(`capture record missing required keys: ${missing.join(',')}`);
|
|
246
|
+
if (record.event !== sourcePayload.hook_event_name) throw new Error(`capture event mismatch: ${record.event}`);
|
|
247
|
+
if (record.command !== sourcePayload.tool_input.command) throw new Error('capture command mismatch');
|
|
248
|
+
if (record.tool_use_id !== sourcePayload.tool_use_id) throw new Error('capture tool_use_id mismatch');
|
|
249
|
+
if (record.session_id !== sourcePayload.session_id) throw new Error('capture session_id mismatch');
|
|
250
|
+
if (record.cwd !== sourcePayload.cwd) throw new Error('capture cwd mismatch');
|
|
251
|
+
if (!HEX64.test(String(record.responseDigest))) throw new Error(`capture responseDigest is not sha256 hex`);
|
|
252
|
+
if (!String(record.responseRef).length) throw new Error('capture responseRef is empty');
|
|
253
|
+
if (sourcePayload.prompt_id === undefined) {
|
|
254
|
+
if ('prompt_id' in record) throw new Error('capture invented prompt_id for absent input');
|
|
255
|
+
} else if (record.prompt_id !== sourcePayload.prompt_id) {
|
|
256
|
+
throw new Error('capture prompt_id mismatch');
|
|
257
|
+
}
|
|
258
|
+
for (const key of ['agent_id', 'agent_type']) {
|
|
259
|
+
if (sourcePayload[key] === undefined) {
|
|
260
|
+
if (key in record) throw new Error(`capture invented ${key} for absent input`);
|
|
261
|
+
} else if (record[key] !== sourcePayload[key]) {
|
|
262
|
+
throw new Error(`capture ${key} mismatch`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (sourcePayload.duration_ms === undefined) {
|
|
266
|
+
if ('duration_ms' in record) throw new Error('capture invented duration_ms for absent input');
|
|
267
|
+
} else if (record.duration_ms !== sourcePayload.duration_ms) {
|
|
268
|
+
throw new Error('capture duration_ms mismatch');
|
|
269
|
+
}
|
|
270
|
+
if (record.event === 'PostToolUse') {
|
|
271
|
+
if (typeof record.interrupted !== 'boolean') throw new Error('capture interrupted is not boolean');
|
|
272
|
+
const expected = Boolean(sourcePayload.tool_response && sourcePayload.tool_response.interrupted);
|
|
273
|
+
if (record.interrupted !== expected) throw new Error('capture interrupted mismatch');
|
|
274
|
+
if ('is_interrupt' in record) throw new Error('capture success record invented is_interrupt');
|
|
275
|
+
if ('failureDisposition' in record) throw new Error('capture success record invented failureDisposition');
|
|
276
|
+
} else if (record.event === 'PostToolUseFailure') {
|
|
277
|
+
if (typeof record.failureDisposition !== 'string' || !record.failureDisposition.length) {
|
|
278
|
+
throw new Error('capture failureDisposition missing');
|
|
279
|
+
}
|
|
280
|
+
if (typeof record.is_interrupt !== 'boolean') throw new Error('capture is_interrupt is not boolean');
|
|
281
|
+
if (sourcePayload.is_interrupt === undefined) throw new Error('fixture error: failure payload must supply is_interrupt');
|
|
282
|
+
if (record.is_interrupt !== sourcePayload.is_interrupt) throw new Error('capture is_interrupt mismatch');
|
|
283
|
+
}
|
|
284
|
+
return record;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ---- Provider input builders -------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
const FIXED = {
|
|
290
|
+
sessionId: 'r2c1-sess-a',
|
|
291
|
+
workflowId: 'wf-r2c1',
|
|
292
|
+
workflowRevision: 0,
|
|
293
|
+
taskId: 'task-r2c1',
|
|
294
|
+
workspaceRoot: '/tmp/r2c1-provider-ws',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// The labelled verdict line of a final review artifact. Mirrors the capture vocabulary only, so the fixture
|
|
298
|
+
// carries the same explicit verdict a real reviewer must state. Free text such as `REJECT: ...` carries none.
|
|
299
|
+
const REVIEW_VERDICT_LINE = /^\s*Review verdict:\s*(approve|reject)\b[.:]?\s*(.*)$/im;
|
|
300
|
+
// Mirrors the provider's `Reviewed artifact: <path> sha256:<hex>` binding (F6): the lines become digests, not summary text.
|
|
301
|
+
const REVIEWED_ARTIFACT_LINE = /^\s*-?\s*Reviewed artifact:\s*(\S+)\s+sha256:([0-9a-f]{64})\s*$/i;
|
|
302
|
+
function reviewVerdictOf(message) {
|
|
303
|
+
const text = String(message || '');
|
|
304
|
+
const match = REVIEW_VERDICT_LINE.exec(text);
|
|
305
|
+
if (!match) return { reviewVerdict: null, reviewSummary: null, reviewedArtifactDigests: {} };
|
|
306
|
+
const reviewedArtifactDigests = {};
|
|
307
|
+
const summary = [match[2], text.slice(match.index + match[0].length)].join('\n').split(/\r?\n/)
|
|
308
|
+
.filter(line => {
|
|
309
|
+
const reviewed = REVIEWED_ARTIFACT_LINE.exec(line);
|
|
310
|
+
if (reviewed) reviewedArtifactDigests[reviewed[1]] = reviewed[2].toLowerCase();
|
|
311
|
+
return !reviewed;
|
|
312
|
+
}).join('\n').trim();
|
|
313
|
+
return { reviewVerdict: match[1].toLowerCase(), reviewSummary: summary, reviewedArtifactDigests };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Simulated capture journal entry built directly from a payload fixture, using
|
|
317
|
+
// the declared capture schema; the digest binds payload.tool_response bytes.
|
|
318
|
+
// opts: { ref, agentId, sessionId, interrupted, toolUseId, trusted }
|
|
319
|
+
function simulateCapture(name, opts = {}) {
|
|
320
|
+
const source = payload(name);
|
|
321
|
+
const sessionId = opts.sessionId || FIXED.sessionId;
|
|
322
|
+
const record = {
|
|
323
|
+
captureId: `${name}:${opts.ref || 'primary'}`,
|
|
324
|
+
ref: opts.ref,
|
|
325
|
+
event: source.hook_event_name,
|
|
326
|
+
command: source.tool_input && typeof source.tool_input === 'object' ? source.tool_input.command : '',
|
|
327
|
+
cwd: source.cwd,
|
|
328
|
+
tool_use_id: opts.toolUseId || source.tool_use_id,
|
|
329
|
+
session_id: opts.sessionId || source.session_id,
|
|
330
|
+
responseDigest: sha256(stable(source.tool_response)),
|
|
331
|
+
responseRef: `payload/${name}`,
|
|
332
|
+
};
|
|
333
|
+
if (source.prompt_id !== undefined) record.prompt_id = source.prompt_id;
|
|
334
|
+
// Mirrors the capture builder: the launch tool name separates the Agent/Task result surface (F4/F5).
|
|
335
|
+
if (typeof source.tool_name === 'string') record.tool_name = source.tool_name;
|
|
336
|
+
const agentId = opts.agentId || source.agent_id;
|
|
337
|
+
if (agentId !== undefined) {
|
|
338
|
+
record.agent_id = agentId;
|
|
339
|
+
record.agent_type = source.agent_type;
|
|
340
|
+
}
|
|
341
|
+
if (source.duration_ms !== undefined) record.duration_ms = source.duration_ms;
|
|
342
|
+
if (source.hook_event_name === 'PostToolUse') {
|
|
343
|
+
// An absent interrupted field stays absent: unknown, not false (A3 section 1, same rule as the capture builder).
|
|
344
|
+
const hostInterrupted = source.tool_response && typeof source.tool_response.interrupted === 'boolean' ? source.tool_response.interrupted : undefined;
|
|
345
|
+
const interrupted = opts.interrupted !== undefined ? Boolean(opts.interrupted) : hostInterrupted;
|
|
346
|
+
if (interrupted !== undefined) record.interrupted = interrupted;
|
|
347
|
+
} else if (source.hook_event_name === 'PostToolUseFailure') {
|
|
348
|
+
record.is_interrupt = Boolean(source.is_interrupt);
|
|
349
|
+
record.failureDisposition = /Exit code (\d+)/.exec(String(source.error || '')) ? 'nonzero-exit' : 'host-failure-error';
|
|
350
|
+
} else if (source.hook_event_name === 'SubagentStop') {
|
|
351
|
+
record.reviewDigest = sha256(source.last_assistant_message || '');
|
|
352
|
+
// The explicit structured verdict is part of the capture record (S2b vocabulary): a labelled
|
|
353
|
+
// `Review verdict: approve|reject` line in the final review artifact. Free text carries none.
|
|
354
|
+
Object.assign(record, reviewVerdictOf(source.last_assistant_message));
|
|
355
|
+
}
|
|
356
|
+
if (opts.trusted !== undefined) record.trusted = opts.trusted;
|
|
357
|
+
return record;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Explicitly fake qualification for dependency injection only. The production
|
|
361
|
+
// path passes no qualification and must resolve the retained live dir instead.
|
|
362
|
+
function fakeQualification(ref) {
|
|
363
|
+
return { source: 'fake-test-fixture', faked: true, ref, toolUseIdPopulated: true, agentIdPopulated: true };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function providerInput(overrides = {}) {
|
|
367
|
+
const executors = overrides.executorPrincipals || [];
|
|
368
|
+
return {
|
|
369
|
+
hostId: 'claude-code',
|
|
370
|
+
sessionId: FIXED.sessionId,
|
|
371
|
+
workspaceRoot: FIXED.workspaceRoot,
|
|
372
|
+
taskId: FIXED.taskId,
|
|
373
|
+
workflowId: FIXED.workflowId,
|
|
374
|
+
workflowRevision: FIXED.workflowRevision,
|
|
375
|
+
principals: { author: FIXED.sessionId, executors },
|
|
376
|
+
captures: [],
|
|
377
|
+
...overrides,
|
|
378
|
+
principals: { author: FIXED.sessionId, executors },
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Minimal S2a-shaped identity for provider event assertions; integration with
|
|
383
|
+
// the full adapter identityset is a later packet's contract.
|
|
384
|
+
function providerIdentity() {
|
|
385
|
+
return {
|
|
386
|
+
sessionId: FIXED.sessionId,
|
|
387
|
+
workflowId: FIXED.workflowId,
|
|
388
|
+
workflowRevision: FIXED.workflowRevision,
|
|
389
|
+
taskId: FIXED.taskId,
|
|
390
|
+
prdRoot: 'docs/prd/',
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = {
|
|
395
|
+
ROOT,
|
|
396
|
+
RETAINED_LIVE_DIR_REL,
|
|
397
|
+
LEGS,
|
|
398
|
+
InfraError,
|
|
399
|
+
CAPTURE_EVENT_NAMES,
|
|
400
|
+
CAPTURE_BASE_REQUIRED,
|
|
401
|
+
FIXED,
|
|
402
|
+
stable,
|
|
403
|
+
sha256,
|
|
404
|
+
payload,
|
|
405
|
+
golden,
|
|
406
|
+
createRunner,
|
|
407
|
+
requirementsMet,
|
|
408
|
+
driveStatePersist,
|
|
409
|
+
findCaptureRecords,
|
|
410
|
+
assertCaptureRecordShape,
|
|
411
|
+
simulateCapture,
|
|
412
|
+
fakeQualification,
|
|
413
|
+
providerInput,
|
|
414
|
+
providerIdentity,
|
|
415
|
+
};
|
package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-failure.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "PostToolUseFailure",
|
|
5
|
+
"tool_name": "Bash",
|
|
6
|
+
"tool_input": { "command": "npm test" },
|
|
7
|
+
"tool_use_id": "toolu_r2c1_b",
|
|
8
|
+
"error": "Exit code 2\nnpm ERR! r2c1 simulated failure",
|
|
9
|
+
"is_interrupt": false,
|
|
10
|
+
"duration_ms": 4187
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "PostToolUse",
|
|
5
|
+
"tool_name": "Bash",
|
|
6
|
+
"tool_input": { "command": "npm test" },
|
|
7
|
+
"tool_response": { "stdout": "ok", "stderr": "", "interrupted": false, "isImage": false },
|
|
8
|
+
"tool_use_id": "toolu_r2c1_c"
|
|
9
|
+
}
|
package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
4
|
+
"transcript_path": "/tmp/r2c1/transcript-a.jsonl",
|
|
5
|
+
"cwd": "/tmp/r2c1-ws",
|
|
6
|
+
"permission_mode": "default",
|
|
7
|
+
"hook_event_name": "PostToolUse",
|
|
8
|
+
"tool_name": "Bash",
|
|
9
|
+
"tool_input": { "command": "npm test", "description": "Run test suite" },
|
|
10
|
+
"tool_response": { "stdout": "ok", "stderr": "", "interrupted": false, "isImage": false },
|
|
11
|
+
"tool_use_id": "toolu_r2c1_a",
|
|
12
|
+
"duration_ms": 12
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "SubagentStop",
|
|
5
|
+
"stop_hook_active": false,
|
|
6
|
+
"agent_id": "agent-r2c1-rev",
|
|
7
|
+
"agent_type": "repo-docs-reviewer",
|
|
8
|
+
"agent_transcript_path": "/tmp/r2c1/subagents/agent-r2c1-rev.jsonl",
|
|
9
|
+
"transcript_path": "/tmp/r2c1/transcript-a.jsonl",
|
|
10
|
+
"last_assistant_message": "REJECT: the document is incorrect."
|
|
11
|
+
}
|
package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-reject.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "SubagentStop",
|
|
5
|
+
"stop_hook_active": false,
|
|
6
|
+
"agent_id": "agent-r2c1-rev",
|
|
7
|
+
"agent_type": "repo-docs-reviewer",
|
|
8
|
+
"agent_transcript_path": "/tmp/r2c1/subagents/agent-r2c1-rev.jsonl",
|
|
9
|
+
"transcript_path": "/tmp/r2c1/transcript-a.jsonl",
|
|
10
|
+
"last_assistant_message": "Review verdict: reject. The governed PRD revision contradicts the acceptance criteria it cites."
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "SubagentStop",
|
|
5
|
+
"stop_hook_active": false,
|
|
6
|
+
"agent_id": "agent-r2c1-rev",
|
|
7
|
+
"agent_type": "repo-docs-reviewer",
|
|
8
|
+
"agent_transcript_path": "/tmp/r2c1/subagents/agent-r2c1-rev.jsonl",
|
|
9
|
+
"transcript_path": "/tmp/r2c1/transcript-a.jsonl",
|
|
10
|
+
"last_assistant_message": "Review verdict: approve. The governed PRD revision keeps acceptance criteria consistent; no unresolved conflicts found.\nReviewed artifact: docs/prd/graph.md sha256:4d2b4ef924ba492f4de058d00eb7a27c91f0bad64f5fcb2eab28fb36e048ec6b"
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "r2c1-sess-a",
|
|
3
|
+
"cwd": "/tmp/r2c1-ws",
|
|
4
|
+
"hook_event_name": "PostToolUse",
|
|
5
|
+
"tool_name": "Task",
|
|
6
|
+
"tool_use_id": "toolu_r2c1_task",
|
|
7
|
+
"duration_ms": 64000,
|
|
8
|
+
"tool_input": {
|
|
9
|
+
"description": "Independent quality review of the governed PRD revision",
|
|
10
|
+
"subagent_type": "repo-docs-reviewer"
|
|
11
|
+
},
|
|
12
|
+
"tool_response": {
|
|
13
|
+
"agentId": "agent-r2c1-rev",
|
|
14
|
+
"resolvedModel": "claude-haiku-4-5-20251001",
|
|
15
|
+
"totalTokens": 1234,
|
|
16
|
+
"outputFile": "/tmp/r2c1/subagents/agent-r2c1-rev.jsonl",
|
|
17
|
+
"result": "Review verdict: approve. The governed PRD revision keeps acceptance criteria consistent; no unresolved conflicts found.\nReviewed artifact: docs/prd/graph.md sha256:4d2b4ef924ba492f4de058d00eb7a27c91f0bad64f5fcb2eab28fb36e048ec6b"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "off-capture-only",
|
|
4
|
+
"mode": "off",
|
|
5
|
+
"events": [
|
|
6
|
+
{
|
|
7
|
+
"id": "event-01",
|
|
8
|
+
"event": "PreToolUse",
|
|
9
|
+
"toolName": "Bash",
|
|
10
|
+
"command": "npm test",
|
|
11
|
+
"toolUseId": "tool-use-1",
|
|
12
|
+
"cwd": "{{WORKSPACE}}",
|
|
13
|
+
"success": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "event-02",
|
|
17
|
+
"event": "PreToolUse",
|
|
18
|
+
"toolName": "Task",
|
|
19
|
+
"command": "task",
|
|
20
|
+
"toolUseId": "tool-use-2",
|
|
21
|
+
"cwd": "{{WORKSPACE}}",
|
|
22
|
+
"success": true
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "event-03",
|
|
26
|
+
"event": "PreToolUse",
|
|
27
|
+
"toolName": "Agent",
|
|
28
|
+
"command": "agent",
|
|
29
|
+
"toolUseId": "tool-use-3",
|
|
30
|
+
"cwd": "{{WORKSPACE}}",
|
|
31
|
+
"success": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "event-04",
|
|
35
|
+
"event": "PostToolUse",
|
|
36
|
+
"toolName": "Task",
|
|
37
|
+
"command": "task",
|
|
38
|
+
"toolUseId": "tool-use-4",
|
|
39
|
+
"cwd": "{{WORKSPACE}}",
|
|
40
|
+
"success": true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "event-05",
|
|
44
|
+
"event": "PostToolUse",
|
|
45
|
+
"toolName": "Agent",
|
|
46
|
+
"command": "agent",
|
|
47
|
+
"toolUseId": "tool-use-5",
|
|
48
|
+
"cwd": "{{WORKSPACE}}",
|
|
49
|
+
"success": true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "event-06",
|
|
53
|
+
"event": "SubagentStart",
|
|
54
|
+
"toolUseId": "tool-use-6",
|
|
55
|
+
"cwd": "{{WORKSPACE}}",
|
|
56
|
+
"success": true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "event-07",
|
|
60
|
+
"event": "SubagentStop",
|
|
61
|
+
"toolUseId": "tool-use-7",
|
|
62
|
+
"cwd": "{{WORKSPACE}}",
|
|
63
|
+
"success": true
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"expectations": {
|
|
67
|
+
"moduleLoads": {
|
|
68
|
+
"invariant": "I1",
|
|
69
|
+
"outcome": "pilot modules may remain mounted; OFF gate returns before pilot work"
|
|
70
|
+
},
|
|
71
|
+
"stateFiles": {
|
|
72
|
+
"invariant": "I1",
|
|
73
|
+
"outcome": "legacy workflow state only; no pilot session binding or report file"
|
|
74
|
+
},
|
|
75
|
+
"captureRecords": {
|
|
76
|
+
"invariant": "I1-I3",
|
|
77
|
+
"outcome": "no pilot capture journal or records"
|
|
78
|
+
},
|
|
79
|
+
"pilotReport": {
|
|
80
|
+
"invariant": "I1-I3",
|
|
81
|
+
"outcome": "absent"
|
|
82
|
+
},
|
|
83
|
+
"workflowStep": {
|
|
84
|
+
"invariant": "I2-I3",
|
|
85
|
+
"outcome": "legacy step behavior is preserved"
|
|
86
|
+
},
|
|
87
|
+
"workflowStop": {
|
|
88
|
+
"invariant": "I4",
|
|
89
|
+
"outcome": "exit 0; reminder/stop output reflects unresolved workflow state without a persistent Harness lock"
|
|
90
|
+
},
|
|
91
|
+
"genericStop": {
|
|
92
|
+
"invariant": "I4",
|
|
93
|
+
"outcome": "exit 0; generic verification behavior remains independent of document credit"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"sources": [
|
|
97
|
+
"synthetic stream derived from F4-Z1 §4 minimal covering set"
|
|
98
|
+
]
|
|
99
|
+
}
|