harness-everything 0.9.0 → 0.11.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 (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +52 -0
  4. package/ci/contract-integrity.test.js +203 -0
  5. package/ci/self-evolve-loop.test.js +311 -0
  6. package/ci/telemetry.test.js +193 -0
  7. package/contract-integrity/fixtures/consistent-trace.json +94 -0
  8. package/contract-integrity/fixtures/reconciled-trace.json +105 -0
  9. package/contract-integrity/fixtures/ticket-drift-trace.json +94 -0
  10. package/contract-integrity/schemas/trace.schema.json +77 -0
  11. package/contract-integrity/scripts/audit.js +536 -0
  12. package/docs/contract-integrity.md +109 -0
  13. package/docs/telemetry.md +62 -0
  14. package/hooks/hooks.json +43 -0
  15. package/hooks/scripts/contract-test.js +24 -0
  16. package/hooks/scripts/lib/learning-opportunity.js +148 -0
  17. package/hooks/scripts/lib/telemetry.js +377 -0
  18. package/hooks/scripts/rule-of-3-tracker.js +23 -0
  19. package/hooks/scripts/state-persist.js +5 -0
  20. package/hooks/scripts/telemetry-adapter.js +25 -0
  21. package/multi-agent-workspace/SKILL.md +1 -1
  22. package/multi-agent-workspace/scripts/index_memory.js +2 -0
  23. package/opencode-plugin/index.mjs +213 -1
  24. package/opencode-plugin/plugin.json +1 -1
  25. package/package.json +7 -2
  26. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  27. package/plugins/harness-everything/hooks/hooks.json +35 -0
  28. package/plugins/harness-everything/hooks/scripts/contract-test.js +24 -0
  29. package/plugins/harness-everything/hooks/scripts/lib/learning-opportunity.js +148 -0
  30. package/plugins/harness-everything/hooks/scripts/lib/telemetry.js +377 -0
  31. package/plugins/harness-everything/hooks/scripts/rule-of-3-tracker.js +23 -0
  32. package/plugins/harness-everything/hooks/scripts/state-persist.js +5 -0
  33. package/plugins/harness-everything/hooks/scripts/telemetry-adapter.js +25 -0
  34. package/plugins/harness-everything/plugin.json +1 -1
  35. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
  36. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +2 -0
  37. package/plugins/harness-everything/skills/self-evolve/SKILL.md +14 -19
  38. package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +32 -13
  39. package/plugins/harness-everything/skills/self-evolve/schemas/lesson-candidate.schema.json +101 -0
  40. package/plugins/harness-everything/skills/self-evolve/scripts/lesson-candidate.js +303 -0
  41. package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +4 -1
  42. package/self-evolve/SKILL.md +14 -19
  43. package/self-evolve/references/memory-resolution.md +32 -13
  44. package/self-evolve/schemas/lesson-candidate.schema.json +101 -0
  45. package/self-evolve/scripts/lesson-candidate.js +303 -0
  46. package/self-evolve/scripts/self-regression.js +4 -1
  47. package/telemetry/schemas/event.schema.json +39 -0
  48. package/telemetry/scripts/benchmark.js +47 -0
  49. package/telemetry/scripts/report.js +133 -0
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { pathToFileURL } = require('url');
8
+ const { spawnSync } = require('child_process');
9
+
10
+ const ROOT = path.resolve(__dirname, '..');
11
+ const telemetry = require(path.join(ROOT, 'hooks/scripts/lib/telemetry.js'));
12
+ const { buildReport } = require(path.join(ROOT, 'telemetry/scripts/report.js'));
13
+ const { runBenchmark } = require(path.join(ROOT, 'telemetry/scripts/benchmark.js'));
14
+
15
+ let failed = 0;
16
+ function check(condition, message, detail = '') {
17
+ if (condition) console.log(` PASS ${message}`);
18
+ else { console.error(` FAIL ${message}${detail ? ` (${detail})` : ''}`); failed++; }
19
+ }
20
+ function run(script, payload, cwd, env = {}) {
21
+ return spawnSync(process.execPath, [path.join(ROOT, script)], {
22
+ cwd,
23
+ input: JSON.stringify(payload),
24
+ encoding: 'utf8',
25
+ env: { ...process.env, HARNESS_TELEMETRY: 'local', ...env },
26
+ });
27
+ }
28
+
29
+ async function main() {
30
+ console.log('=== Cross-Agent Telemetry (#83) ===');
31
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-telemetry-test-'));
32
+ const workspace = path.join(temp, 'workspace');
33
+ const stateHome = path.join(temp, 'state-home');
34
+ fs.mkdirSync(workspace, { recursive: true });
35
+ const git = spawnSync('git', ['init'], { cwd: workspace, encoding: 'utf8' });
36
+ check(git.status === 0, 'telemetry fixture initializes git workspace', git.stderr);
37
+
38
+ const priorHome = process.env.HARNESS_STATE_HOME;
39
+ const priorTelemetry = process.env.HARNESS_TELEMETRY;
40
+ process.env.HARNESS_STATE_HOME = stateHome;
41
+ process.env.HARNESS_TELEMETRY = 'local';
42
+
43
+ try {
44
+ const session = 'raw-session-id-must-be-hashed';
45
+ const secret = 'SECRET_TOOL_ARGUMENT_MUST_NOT_APPEAR';
46
+ const base = {
47
+ session_id: session,
48
+ cwd: workspace,
49
+ tool_name: 'Skill',
50
+ tool_use_id: 'skill-call-1',
51
+ tool_input: { name: 'tdd', hidden: secret },
52
+ };
53
+
54
+ const pre = run('hooks/scripts/telemetry-adapter.js', { ...base, hook_event_name: 'PreToolUse' }, workspace, {
55
+ HARNESS_STATE_HOME: stateHome,
56
+ });
57
+ check(pre.status === 0, 'Claude-compatible skill pre hook is fail-open', pre.stderr);
58
+
59
+ const post = run('hooks/scripts/telemetry-adapter.js', {
60
+ ...base,
61
+ hook_event_name: 'PostToolUse',
62
+ tool_response: { exitCode: 0, stdout: secret },
63
+ }, workspace, { HARNESS_STATE_HOME: stateHome });
64
+ check(post.status === 0, 'Claude-compatible skill post hook is fail-open', post.stderr);
65
+
66
+ const tool = run('hooks/scripts/state-persist.js', {
67
+ session_id: session,
68
+ cwd: workspace,
69
+ hook_event_name: 'PostToolUse',
70
+ tool_name: 'Bash',
71
+ tool_use_id: 'tool-call-1',
72
+ tool_input: { command: `echo ${secret}` },
73
+ tool_response: { exitCode: 0, duration_ms: 12.5, stdout: secret },
74
+ }, workspace, { HARNESS_STATE_HOME: stateHome });
75
+ check(tool.status === 0, 'existing state-persist hook remains successful with telemetry observer', tool.stderr);
76
+
77
+ const stop = run('hooks/scripts/telemetry-adapter.js', {
78
+ session_id: session,
79
+ cwd: workspace,
80
+ hook_event_name: 'Stop',
81
+ }, workspace, { HARNESS_STATE_HOME: stateHome });
82
+ check(stop.status === 0, 'Stop closes active telemetry window without gating task', stop.stderr);
83
+
84
+ const file = telemetry.telemetryFile(workspace, { cwd: workspace });
85
+ const events = telemetry.readEvents(file);
86
+ check(events.length === 4, 'skill invoke/load, tool observation, and completion are retained as four normalized events');
87
+ check(events.every(event => !event.invalid), 'all retained JSONL events satisfy the normalized event contract');
88
+ check(events.every(event => event.sessionId !== session && /^session-[a-f0-9]{16}$/.test(event.sessionId)), 'raw host session identity is replaced by local hash');
89
+ const serialized = fs.readFileSync(file, 'utf8');
90
+ check(!serialized.includes(secret), 'telemetry never stores prompt/tool argument/output secret fixture');
91
+ check(!serialized.includes(workspace), 'telemetry never stores absolute workspace paths');
92
+ check(!serialized.includes('command'), 'telemetry event payload has no raw command field');
93
+
94
+ const toolEvent = events.find(event => event.event === 'tool.observed');
95
+ check(toolEvent?.timing?.attributedToolDurationMs === 12.5, 'host-provided tool duration is attributed separately while skill is active');
96
+ const complete = events.find(event => event.event === 'skill.complete');
97
+ check(Number.isFinite(complete?.timing?.activeWindowMs), 'active window is recorded independently from skill load/tool time');
98
+ check(Number.isFinite(events.find(event => event.event === 'skill.loaded')?.timing?.skillLoadDurationMs), 'skill load duration has its own metric');
99
+
100
+ const report = buildReport(events);
101
+ check(report.skills.length === 1 && report.skills[0].skillName === 'tdd', 'aggregation groups events by host/skill');
102
+ check(report.skills[0].invocationCount === 1 && report.skills[0].toolEventCount === 1, 'aggregation retains invocation and attributed-tool counts');
103
+ check(report.semantics.activeWindowMs.includes('not CPU'), 'report documents active-window semantics instead of calling it runtime');
104
+
105
+ const forbidden = telemetry.emitTelemetry({
106
+ event: 'skill.invoke',
107
+ host: 'claude',
108
+ prompt: 'must never be accepted',
109
+ }, { file: path.join(temp, 'forbidden.jsonl') });
110
+ check(forbidden.ok === false && /forbidden/.test(forbidden.error), 'privacy contract rejects forbidden content-bearing fields');
111
+
112
+ const impossibleParent = path.join(temp, 'parent-is-file');
113
+ fs.writeFileSync(impossibleParent, 'x', 'utf8');
114
+ const storageFailure = telemetry.emitTelemetry({
115
+ event: 'skill.invoke',
116
+ host: 'claude',
117
+ sessionId: 'session-test',
118
+ invocationId: 'invocation-test',
119
+ skillName: 'tdd',
120
+ }, { file: path.join(impossibleParent, 'events.jsonl') });
121
+ check(storageFailure.ok === false, 'telemetry storage failure is returned as observability failure rather than throwing');
122
+
123
+ process.env.HARNESS_TELEMETRY = 'off';
124
+ const beforeDisabled = fs.statSync(file).size;
125
+ const disabled = telemetry.emitTelemetry({
126
+ event: 'skill.invoke', host: 'claude', skillName: 'tdd',
127
+ }, { file });
128
+ check(disabled.ok === true && disabled.disabled === true && fs.statSync(file).size === beforeDisabled, 'telemetry can be locally disabled without task failure');
129
+ process.env.HARNESS_TELEMETRY = 'local';
130
+
131
+ // OpenCode: use the real plugin hooks. No extra process is spawned for
132
+ // per-tool timing; before/after timestamps live inside the plugin process.
133
+ const skillDir = path.join(workspace, '.claude', 'skills', 'tdd');
134
+ fs.mkdirSync(skillDir, { recursive: true });
135
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), '---\nname: tdd\nmetadata:\n version: 9.9.9\n---\n', 'utf8');
136
+ const pluginUrl = pathToFileURL(path.join(ROOT, 'opencode-plugin', 'index.mjs')).href + `?telemetry=${Date.now()}`;
137
+ const { HarnessEnforcement } = await import(pluginUrl);
138
+ const hooks = await HarnessEnforcement({
139
+ directory: workspace,
140
+ client: { session: { prompt: async () => {} } },
141
+ });
142
+ await hooks['tool.execute.before']({ tool: 'skill', sessionID: 'oc-session', callID: 'oc-skill-1', args: { name: 'tdd', secret } }, {});
143
+ await hooks['tool.execute.after']({ tool: 'skill', sessionID: 'oc-session', callID: 'oc-skill-1', args: { name: 'tdd' } }, {});
144
+ await hooks['tool.execute.before']({ tool: 'bash', sessionID: 'oc-session', callID: 'oc-tool-1', args: { command: secret } }, {});
145
+ await new Promise(resolve => setTimeout(resolve, 2));
146
+ await hooks['tool.execute.after']({ tool: 'bash', sessionID: 'oc-session', callID: 'oc-tool-1', args: { command: secret } }, {});
147
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 'oc-session' } } });
148
+
149
+ const allEvents = telemetry.readEvents(file);
150
+ const ocEvents = allEvents.filter(event => event.host === 'opencode');
151
+ check(ocEvents.some(event => event.event === 'skill.invoke') && ocEvents.some(event => event.event === 'skill.complete'), 'real OpenCode plugin emits skill invocation and turn-boundary completion events');
152
+ check(ocEvents.some(event => event.event === 'tool.observed' && Number.isFinite(event.timing.attributedToolDurationMs)), 'OpenCode adapter measures in-process tool duration while skill is active');
153
+ check(ocEvents.some(event => event.skillVersion === '9.9.9'), 'OpenCode adapter records locally resolved skill version without storing skill-file path');
154
+ check(!fs.readFileSync(file, 'utf8').includes(secret), 'OpenCode telemetry also excludes raw tool arguments');
155
+
156
+ const benchmark = runBenchmark(120);
157
+ check(Number.isFinite(benchmark.p50Ms) && Number.isFinite(benchmark.p95Ms) && benchmark.count === 120, 'instrumentation benchmark measures local append overhead');
158
+ console.log(` INFO telemetry local sink overhead: p50=${benchmark.p50Ms.toFixed(3)}ms p95=${benchmark.p95Ms.toFixed(3)}ms max=${benchmark.maxMs.toFixed(3)}ms`);
159
+
160
+ const sourceHooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'hooks/hooks.json'), 'utf8'));
161
+ const pluginHooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'plugins/harness-everything/hooks/hooks.json'), 'utf8'));
162
+ check(sourceHooks.hooks.PreToolUse.some(group => group.matcher === 'Skill' && group.id === 'harness:pre:skill-telemetry'), 'Claude manifest wires narrow Skill pre telemetry hook');
163
+ check(sourceHooks.hooks.Stop.some(group => group.id === 'harness:stop:skill-telemetry'), 'Claude manifest closes active windows at Stop');
164
+ check(pluginHooks.hooks.PreToolUse.some(group => group.matcher === 'Skill' && group.hooks.some(h => /telemetry-adapter/.test(h.command))), 'OpenAI package contains mechanism-level Skill telemetry adapter');
165
+ check(!pluginHooks.hooks.PostToolUseFailure, 'Codex package does not invent unsupported PostToolUseFailure telemetry');
166
+
167
+ for (const [canonical, mirror] of [
168
+ ['hooks/scripts/lib/telemetry.js', 'plugins/harness-everything/hooks/scripts/lib/telemetry.js'],
169
+ ['hooks/scripts/telemetry-adapter.js', 'plugins/harness-everything/hooks/scripts/telemetry-adapter.js'],
170
+ ['hooks/scripts/state-persist.js', 'plugins/harness-everything/hooks/scripts/state-persist.js'],
171
+ ]) {
172
+ check(fs.readFileSync(path.join(ROOT, canonical), 'utf8') === fs.readFileSync(path.join(ROOT, mirror), 'utf8'), `plugin mirror matches canonical: ${canonical}`);
173
+ }
174
+
175
+ const schema = JSON.parse(fs.readFileSync(path.join(ROOT, 'telemetry/schemas/event.schema.json'), 'utf8'));
176
+ check(schema.properties?.timing?.properties?.activeWindowMs && schema.properties?.event?.enum?.includes('tool.observed'), 'versioned telemetry schema preserves separate timing vocabulary');
177
+
178
+ } finally {
179
+ if (priorHome === undefined) delete process.env.HARNESS_STATE_HOME;
180
+ else process.env.HARNESS_STATE_HOME = priorHome;
181
+ if (priorTelemetry === undefined) delete process.env.HARNESS_TELEMETRY;
182
+ else process.env.HARNESS_TELEMETRY = priorTelemetry;
183
+ fs.rmSync(temp, { recursive: true, force: true });
184
+ }
185
+
186
+ console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: #83 cross-agent telemetry (${failed} failure${failed === 1 ? '' : 's'})`);
187
+ process.exit(failed === 0 ? 0 : 1);
188
+ }
189
+
190
+ main().catch(error => {
191
+ console.error(error);
192
+ process.exit(1);
193
+ });
@@ -0,0 +1,94 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "mode": "strict",
4
+ "artifacts": [
5
+ {
6
+ "id": "ADR-001",
7
+ "kind": "ADR",
8
+ "path": "docs/adr/0001-api-contract.md",
9
+ "status": "ACTIVE",
10
+ "revision": 1,
11
+ "supersedes": [],
12
+ "evidenceRef": null
13
+ },
14
+ {
15
+ "id": "SPEC-001",
16
+ "kind": "SPEC",
17
+ "path": "docs/specs/api.md",
18
+ "status": "ACTIVE",
19
+ "revision": 1,
20
+ "supersedes": [],
21
+ "evidenceRef": null
22
+ },
23
+ {
24
+ "id": "TICKET-001",
25
+ "kind": "TICKET",
26
+ "path": "tickets/001.md",
27
+ "status": "ACTIVE",
28
+ "revision": 1,
29
+ "requirementId": "REQ-001",
30
+ "requirementRevision": 1,
31
+ "changeImpact": "implementation",
32
+ "supersedes": [],
33
+ "evidenceRef": null
34
+ },
35
+ {
36
+ "id": "TEST-001",
37
+ "kind": "TEST",
38
+ "path": "tests/api.test.js",
39
+ "status": "ACTIVE",
40
+ "revision": 1,
41
+ "requirementId": "REQ-001",
42
+ "requirementRevision": 1,
43
+ "supersedes": [],
44
+ "evidenceRef": "tests/api.test.js#contract"
45
+ },
46
+ {
47
+ "id": "IMPL-001",
48
+ "kind": "IMPLEMENTATION",
49
+ "path": "src/api.js",
50
+ "status": "ACTIVE",
51
+ "revision": 1,
52
+ "requirementId": "REQ-001",
53
+ "requirementRevision": 1,
54
+ "supersedes": [],
55
+ "evidenceRef": "commit:baseline"
56
+ }
57
+ ],
58
+ "requirements": [
59
+ {
60
+ "requirementId": "REQ-001",
61
+ "revision": 1,
62
+ "status": "CURRENT",
63
+ "specId": "SPEC-001",
64
+ "decisionIds": [
65
+ "ADR-001"
66
+ ],
67
+ "ticketIds": [
68
+ "TICKET-001"
69
+ ],
70
+ "testIds": [
71
+ "TEST-001"
72
+ ],
73
+ "implementationIds": [
74
+ "IMPL-001"
75
+ ],
76
+ "requiredProbeIds": [
77
+ "PROBE-001"
78
+ ],
79
+ "sourceValidity": "VALID",
80
+ "supersededBy": null
81
+ }
82
+ ],
83
+ "probes": [
84
+ {
85
+ "probeId": "PROBE-001",
86
+ "requirementId": "REQ-001",
87
+ "status": "KILLED",
88
+ "sourceSection": "docs/specs/api.md#REQ-001",
89
+ "evidenceRef": "probe:baseline-red",
90
+ "workspaceIsolation": "isolated",
91
+ "failureClass": "contract"
92
+ }
93
+ ]
94
+ }
@@ -0,0 +1,105 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "mode": "strict",
4
+ "artifacts": [
5
+ {
6
+ "id": "ADR-001",
7
+ "kind": "ADR",
8
+ "path": "docs/adr/0001-api-contract.md",
9
+ "status": "SUPERSEDED",
10
+ "revision": 1,
11
+ "supersedes": [],
12
+ "evidenceRef": null
13
+ },
14
+ {
15
+ "id": "ADR-002",
16
+ "kind": "ADR",
17
+ "path": "docs/adr/0002-api-contract-v2.md",
18
+ "status": "ACTIVE",
19
+ "revision": 2,
20
+ "supersedes": [
21
+ "ADR-001"
22
+ ],
23
+ "evidenceRef": "decision:approved-v2"
24
+ },
25
+ {
26
+ "id": "SPEC-001",
27
+ "kind": "SPEC",
28
+ "path": "docs/specs/api.md",
29
+ "status": "ACTIVE",
30
+ "revision": 2,
31
+ "supersedes": [],
32
+ "evidenceRef": "spec:REQ-001-v2"
33
+ },
34
+ {
35
+ "id": "TICKET-001",
36
+ "kind": "TICKET",
37
+ "path": "tickets/001.md",
38
+ "status": "ACTIVE",
39
+ "revision": 2,
40
+ "requirementId": "REQ-001",
41
+ "requirementRevision": 2,
42
+ "changeImpact": "architecture",
43
+ "supersedes": [],
44
+ "evidenceRef": "feedback:approved-v2"
45
+ },
46
+ {
47
+ "id": "TEST-001",
48
+ "kind": "TEST",
49
+ "path": "tests/api.test.js",
50
+ "status": "ACTIVE",
51
+ "revision": 2,
52
+ "requirementId": "REQ-001",
53
+ "requirementRevision": 2,
54
+ "supersedes": [],
55
+ "evidenceRef": "tests/api.test.js#contract-v2"
56
+ },
57
+ {
58
+ "id": "IMPL-001",
59
+ "kind": "IMPLEMENTATION",
60
+ "path": "src/api.js",
61
+ "status": "ACTIVE",
62
+ "revision": 2,
63
+ "requirementId": "REQ-001",
64
+ "requirementRevision": 2,
65
+ "supersedes": [],
66
+ "evidenceRef": "commit:implemented-v2"
67
+ }
68
+ ],
69
+ "requirements": [
70
+ {
71
+ "requirementId": "REQ-001",
72
+ "revision": 2,
73
+ "status": "CURRENT",
74
+ "specId": "SPEC-001",
75
+ "decisionIds": [
76
+ "ADR-002"
77
+ ],
78
+ "ticketIds": [
79
+ "TICKET-001"
80
+ ],
81
+ "testIds": [
82
+ "TEST-001"
83
+ ],
84
+ "implementationIds": [
85
+ "IMPL-001"
86
+ ],
87
+ "requiredProbeIds": [
88
+ "PROBE-001"
89
+ ],
90
+ "sourceValidity": "VALID",
91
+ "supersededBy": null
92
+ }
93
+ ],
94
+ "probes": [
95
+ {
96
+ "probeId": "PROBE-001",
97
+ "requirementId": "REQ-001",
98
+ "status": "KILLED",
99
+ "sourceSection": "docs/specs/api.md#REQ-001-v2",
100
+ "evidenceRef": "probe:v2-red",
101
+ "workspaceIsolation": "isolated",
102
+ "failureClass": "contract"
103
+ }
104
+ ]
105
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "mode": "strict",
4
+ "artifacts": [
5
+ {
6
+ "id": "ADR-001",
7
+ "kind": "ADR",
8
+ "path": "docs/adr/0001-api-contract.md",
9
+ "status": "ACTIVE",
10
+ "revision": 1,
11
+ "supersedes": [],
12
+ "evidenceRef": null
13
+ },
14
+ {
15
+ "id": "SPEC-001",
16
+ "kind": "SPEC",
17
+ "path": "docs/specs/api.md",
18
+ "status": "ACTIVE",
19
+ "revision": 1,
20
+ "supersedes": [],
21
+ "evidenceRef": null
22
+ },
23
+ {
24
+ "id": "TICKET-001",
25
+ "kind": "TICKET",
26
+ "path": "tickets/001.md",
27
+ "status": "ACTIVE",
28
+ "revision": 2,
29
+ "requirementId": "REQ-001",
30
+ "requirementRevision": 2,
31
+ "changeImpact": "behavior",
32
+ "supersedes": [],
33
+ "evidenceRef": "feedback:changed-acceptance"
34
+ },
35
+ {
36
+ "id": "TEST-001",
37
+ "kind": "TEST",
38
+ "path": "tests/api.test.js",
39
+ "status": "ACTIVE",
40
+ "revision": 1,
41
+ "requirementId": "REQ-001",
42
+ "requirementRevision": 1,
43
+ "supersedes": [],
44
+ "evidenceRef": "tests/api.test.js#contract"
45
+ },
46
+ {
47
+ "id": "IMPL-001",
48
+ "kind": "IMPLEMENTATION",
49
+ "path": "src/api.js",
50
+ "status": "ACTIVE",
51
+ "revision": 1,
52
+ "requirementId": "REQ-001",
53
+ "requirementRevision": 1,
54
+ "supersedes": [],
55
+ "evidenceRef": "commit:baseline"
56
+ }
57
+ ],
58
+ "requirements": [
59
+ {
60
+ "requirementId": "REQ-001",
61
+ "revision": 1,
62
+ "status": "CURRENT",
63
+ "specId": "SPEC-001",
64
+ "decisionIds": [
65
+ "ADR-001"
66
+ ],
67
+ "ticketIds": [
68
+ "TICKET-001"
69
+ ],
70
+ "testIds": [
71
+ "TEST-001"
72
+ ],
73
+ "implementationIds": [
74
+ "IMPL-001"
75
+ ],
76
+ "requiredProbeIds": [
77
+ "PROBE-001"
78
+ ],
79
+ "sourceValidity": "VALID",
80
+ "supersededBy": null
81
+ }
82
+ ],
83
+ "probes": [
84
+ {
85
+ "probeId": "PROBE-001",
86
+ "requirementId": "REQ-001",
87
+ "status": "KILLED",
88
+ "sourceSection": "docs/specs/api.md#REQ-001",
89
+ "evidenceRef": "probe:baseline-red",
90
+ "workspaceIsolation": "isolated",
91
+ "failureClass": "contract"
92
+ }
93
+ ]
94
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/dyphn1/Harness-everything/contract-integrity/schemas/trace.schema.json",
4
+ "title": "Harness Contract Integrity Trace",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "mode", "artifacts", "requirements", "probes"],
7
+ "properties": {
8
+ "schemaVersion": { "const": "1.0.0" },
9
+ "mode": { "enum": ["audit", "strict"] },
10
+ "artifacts": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["id", "kind", "path", "status", "revision"],
15
+ "properties": {
16
+ "id": { "type": "string", "minLength": 1 },
17
+ "kind": { "enum": ["ADR", "SPEC", "TICKET", "TEST", "IMPLEMENTATION"] },
18
+ "path": { "type": "string", "minLength": 1 },
19
+ "status": { "enum": ["ACTIVE", "SUPERSEDED", "DEPRECATED"] },
20
+ "revision": { "type": "integer", "minimum": 1 },
21
+ "requirementId": { "type": ["string", "null"] },
22
+ "requirementRevision": { "type": ["integer", "null"], "minimum": 1 },
23
+ "changeImpact": { "enum": ["none", "implementation", "behavior", "architecture"] },
24
+ "supersedes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
25
+ "evidenceRef": { "type": ["string", "null"] }
26
+ },
27
+ "additionalProperties": false
28
+ }
29
+ },
30
+ "requirements": {
31
+ "type": "array",
32
+ "minItems": 1,
33
+ "items": {
34
+ "type": "object",
35
+ "required": [
36
+ "requirementId", "revision", "status", "specId", "decisionIds",
37
+ "ticketIds", "testIds", "implementationIds", "requiredProbeIds", "sourceValidity"
38
+ ],
39
+ "properties": {
40
+ "requirementId": { "type": "string", "minLength": 1 },
41
+ "revision": { "type": "integer", "minimum": 1 },
42
+ "status": { "enum": ["CURRENT", "SUPERSEDED"] },
43
+ "specId": { "type": "string", "minLength": 1 },
44
+ "decisionIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
45
+ "ticketIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
46
+ "testIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
47
+ "implementationIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
48
+ "requiredProbeIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
49
+ "sourceValidity": { "enum": ["VALID", "SOURCE_DEFECT", "SOURCE_CONFLICT"] },
50
+ "supersededBy": { "type": ["string", "null"] }
51
+ },
52
+ "additionalProperties": false
53
+ }
54
+ },
55
+ "probes": {
56
+ "type": "array",
57
+ "items": {
58
+ "type": "object",
59
+ "required": [
60
+ "probeId", "requirementId", "status", "sourceSection",
61
+ "evidenceRef", "workspaceIsolation", "failureClass"
62
+ ],
63
+ "properties": {
64
+ "probeId": { "type": "string", "minLength": 1 },
65
+ "requirementId": { "type": "string", "minLength": 1 },
66
+ "status": { "enum": ["KILLED", "SURVIVED", "INVALID", "NOT_EVALUATED"] },
67
+ "sourceSection": { "type": "string", "minLength": 1 },
68
+ "evidenceRef": { "type": ["string", "null"] },
69
+ "workspaceIsolation": { "enum": ["isolated", "not-isolated", "unknown"] },
70
+ "failureClass": { "enum": ["contract", "build", "infrastructure", "flaky", "none", "unknown"] }
71
+ },
72
+ "additionalProperties": false
73
+ }
74
+ }
75
+ },
76
+ "additionalProperties": false
77
+ }