harness-everything 0.30.8 → 0.30.10

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 (30) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/ci.yml +31 -0
  4. package/CHANGELOG.md +21 -0
  5. package/README.md +1 -1
  6. package/benchmarks/results/live-host/codex-2026-10-13/README.md +55 -0
  7. package/benchmarks/results/live-host/codex-2026-10-13/observation-records.json +88 -0
  8. package/ci/doc-capability-consistency.test.js +11 -1
  9. package/ci/fixtures/system-one-resident-stub.py +7 -1
  10. package/ci/mechanism-2al-codex-hooks-compat.test.js +19 -0
  11. package/ci/mechanism-2am-observations-session-import.test.js +221 -0
  12. package/ci/mechanism-2ao-plugin-sync.test.js +129 -4
  13. package/ci/mechanism-2w-platform-compatibility.test.js +12 -0
  14. package/ci/mechanism-34-observations.test.js +64 -0
  15. package/ci/mechanism-system-one-resident.test.js +70 -3
  16. package/docs/platform-capabilities.md +8 -8
  17. package/docs/platform-compatibility.json +5 -5
  18. package/docs/system-one-observations.md +35 -8
  19. package/hooks/scripts/lib/observations.js +88 -12
  20. package/opencode-plugin/plugin.json +1 -1
  21. package/package.json +1 -1
  22. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  23. package/plugins/harness-everything/hooks/scripts/lib/observations.js +88 -12
  24. package/plugins/harness-everything/plugin.json +1 -1
  25. package/sbom.cdx.json +7 -7
  26. package/scripts/codex-hooks-compat.js +39 -14
  27. package/scripts/plugin-sync.js +100 -29
  28. package/scripts/system-one-observations-export.js +29 -2
  29. package/scripts/system-one-observations-import-codex-sessions.js +307 -0
  30. package/scripts/system-one-observations-review.js +11 -1
@@ -10,7 +10,7 @@
10
10
  "name": "harness-everything",
11
11
  "source": "./",
12
12
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
13
- "version": "0.30.8",
13
+ "version": "0.30.10",
14
14
  "author": {
15
15
  "name": "Miya Daniel"
16
16
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-everything",
3
3
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
4
- "version": "0.30.8",
4
+ "version": "0.30.10",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
@@ -29,6 +29,37 @@ jobs:
29
29
  - name: Run focused mutation checks
30
30
  run: npm run test:mutations
31
31
 
32
+ # Pin the patched version: floating Node 24 already passed before #284.
33
+ system-one-windows-node24-patched:
34
+ runs-on: windows-latest
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ - uses: actions/setup-node@v7
38
+ with:
39
+ node-version: '24.20.0'
40
+ - name: Verify exact patched runtime
41
+ run: node -e "if (process.versions.node !== '24.20.0') process.exit(1)"
42
+ - name: Exercise real evaluator subprocess and repeated resident teardown
43
+ run: node --test ci/mechanism-system-one-resident.test.js
44
+
45
+ # Node >=22 also includes the odd-numbered Node 23 line. Keep its
46
+ # documented Windows shutdown failure visible, without narrowing engines.
47
+ system-one-windows-node-legacy-policy:
48
+ runs-on: windows-latest
49
+ strategy:
50
+ fail-fast: false
51
+ matrix:
52
+ node: ['23.6.0', '24.15.0']
53
+ steps:
54
+ - uses: actions/checkout@v7
55
+ - uses: actions/setup-node@v7
56
+ with:
57
+ node-version: ${{ matrix.node }}
58
+ - name: Verify exact affected runtime
59
+ run: node -e "if (process.versions.node !== '${{ matrix.node }}') process.exit(1)"
60
+ - name: Confirm explicit RS13 skip with version-policy unit coverage
61
+ run: node --test --test-name-pattern="S1-RS00|S1-RS13" ci/mechanism-system-one-resident.test.js
62
+
32
63
  runtime-floor:
33
64
  runs-on: ubuntu-latest
34
65
  steps:
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.30.10](https://github.com/dyphn1/Harness-everything/compare/v0.30.9...v0.30.10) (2026-10-13)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **codex:** address observation review findings ([b869cfb](https://github.com/dyphn1/Harness-everything/commit/b869cfbdb44172ee88ad6ae3f76e29c8da6e9da8))
10
+ * **codex:** sync hooks and recover turn labels ([d29112e](https://github.com/dyphn1/Harness-everything/commit/d29112e15ac255c5ded6fcf2aab3a21c710b3def)), closes [#304](https://github.com/dyphn1/Harness-everything/issues/304)
11
+ * **codex:** validate sources and report import failures ([3f57b8b](https://github.com/dyphn1/Harness-everything/commit/3f57b8b376bfd437c8cff6ddbe4ddbf036380198)), closes [#304](https://github.com/dyphn1/Harness-everything/issues/304)
12
+
13
+ ## [Unreleased]
14
+
15
+ ### Bug Fixes
16
+
17
+ * **codex:** keep compatibility hooks and session labels in sync
18
+
19
+ ## [0.30.9](https://github.com/dyphn1/Harness-everything/compare/v0.30.8...v0.30.9) (2026-10-08)
20
+
21
+ ### Bug Fixes
22
+
23
+ * **system-one:** cover Windows Node 23 shutdown regression ([75ef983](https://github.com/dyphn1/Harness-everything/commit/75ef9833e4fcfb697c8323836f08eed3dfc7ff8d))
24
+ * **test:** correct Windows Node version policy regex ([c0b63cc](https://github.com/dyphn1/Harness-everything/commit/c0b63cc3f18dc7126e4680f4fa7682ea2a4729ef))
25
+
5
26
  ## [0.30.8](https://github.com/dyphn1/Harness-everything/compare/v0.30.7...v0.30.8) (2026-10-07)
6
27
 
7
28
  ### Bug Fixes
package/README.md CHANGED
@@ -48,7 +48,7 @@ The exact "Harness" behavior depends on the installation surface. Claude Code ha
48
48
 
49
49
  Harness integrates directly into your workspace. There is no heavy daemon, no paid external APIs, and zero configuration required.
50
50
 
51
- **Runtime:** Harness supports **Node.js 22+**. Node.js **24** is the primary development and CI runtime (`.nvmrc`). The generated current-state runtime/workflow summary is [docs/repository-contract.md](docs/repository-contract.md).
51
+ **Runtime:** Harness supports **Node.js 22+** (including odd-numbered versions); Node.js **24** is the primary development and CI runtime (`.nvmrc`). **On Windows, System One evaluator subprocess verification should use Node 22 or Node 24.20+**. Node 23.x has a documented Windows process-shutdown assertion ([nodejs/node#56645](https://github.com/nodejs/node/issues/56645)); Node 24.0–24.19 predates the backported libuv fix ([nodejs/node#61999](https://github.com/nodejs/node/pull/61999)) and may likewise crash at process exit (`0xC0000409`). RS13 visibly skips **only** on those Windows versions while retaining the real evaluator subprocess elsewhere. A skip is not a pass or evidence of a fixed Node runtime. CI pins Windows Node 23.6.0 and 24.15.0 to validate their skip policy, and Node 24.20.0 to run the complete subprocess/regression suite. These tests do not reduce the package's general `>=22` engine compatibility claim. The generated current-state runtime/workflow summary is [docs/repository-contract.md](docs/repository-contract.md).
52
52
 
53
53
  ```bash
54
54
  # Option A: Claude Code plugin (marketplace manifest included)
@@ -0,0 +1,55 @@
1
+ # Live-host evidence: Codex CLI observation capture
2
+
3
+ This records live dispatch of the Codex user-hook compatibility fallback on one Windows host. It is mechanism/live-host evidence for turn capture, not a behavioral-effectiveness benchmark and not evidence for native plugin-hook mounting.
4
+
5
+ - Date: 2026-10-13 UTC
6
+ - Host: Codex CLI 0.162.1; Windows `10.0.26200` x64; Node.js v24.18.0
7
+ - Repository revision under test: PR #305, `3f57b8b376bfd437c8cff6ddbe4ddbf036380198`
8
+ - Packaged compatibility runtime: version 0.30.9; SHA-256 `7f4e7801d9fdb7350072b01c91034cd28890dfe7190d143ec7760c85fd5a29df`, matching `plugins/harness-everything/`
9
+ - Compatibility status: files healthy; status command reports `configured-unverified` because it cannot read Codex trust state.
10
+
11
+ ## Procedure
12
+
13
+ The Codex `/hooks` UI showed the observation handlers as new/untrusted. I reviewed and trusted only the `UserPromptSubmit` and `Stop` handlers named `Recording a local System One turn observation`. I did not trust all hooks. No trust bypass flag was used. The successful probes ran through `codex exec` with read-only sandboxing and no tool calls. A second turn resumed the same session.
14
+
15
+ Two preliminary attempts were not counted as passing evidence: before trust, a completed CLI turn produced no observation; a later argument-passed prompt lost JSON quotes in Windows `.cmd` argument handling and created an invalid-label record. The accepted run passed its prompt through stdin, preserving the valid JSON label. The probe prompt and raw rollout stay in the private local Codex/observation stores and are not committed here.
16
+
17
+ ## Results
18
+
19
+ The sanitized snapshot is [observation-records.json](observation-records.json). Both records have `host: "codex"`, the same session ID, sequential `turn` values, distinct `turn_id` values, and the self-report expected for their own turn:
20
+
21
+ | Turn | Record ID | Turn ID | Self-report intent | Result |
22
+ |---:|---|---|---|---|
23
+ | 1 | `09ba6878d736e3f7183e7c3a` | `01a1338c-9727-7743-824d-92f8ceb51f00` | `test` | Passed |
24
+ | 2 | `81a4c3a35f7894653aaf9f64` | `01a1338f-6ea2-7671-997f-e9a205b96102` | `discuss` | Passed |
25
+
26
+ The second turn did not reuse the first turn's label. Prompt text, prompt hashes, and raw session content are omitted from this committed artifact. The live records showed zero tool calls, as expected for these reply-only probes; this run does not verify tool-event counting.
27
+
28
+ ## Limits
29
+
30
+ - Verified scope: one Windows Codex CLI 0.162.1 setup using the user-hook compatibility fallback.
31
+ - Not verified: native plugin-bundled hook mounting, marketplace source reporting during `plugin-sync`, other Codex versions/platforms, other lifecycle events, or behavioral effectiveness.
32
+ - The CLI emitted a non-fatal GitHub MCP `AuthRequired` diagnostic and a bundled `SessionEnd` timeout-clamping notice. Both turns completed with exit code 0 and their observation records were present.
33
+
34
+ ## Repository verification
35
+
36
+ All repository gates listed in `AGENTS.md` passed on this PR revision. The four gates affected by the new Codex evidence (`npm test`, `test:mechanism`, `test:consistency`, and `test:docs:capabilities`) initially exposed stale assertions that required Codex live evidence to remain `Unknown`; the assertions were updated to validate the retained artifact, then those gates passed on rerun.
37
+
38
+ | Gate | Result |
39
+ | --- | --- |
40
+ | `npm test` | Passed on rerun; 2 environment-dependent cases skipped |
41
+ | `npm run test:mutations` | Passed |
42
+ | `npm run test:mechanism` | Passed on rerun |
43
+ | `npm run test:consistency` | Passed on rerun |
44
+ | `npm run test:repo-contract` | Passed |
45
+ | `npm run test:docs:capabilities` | Passed on rerun |
46
+ | `npm run test:references` | Passed |
47
+ | `npm run test:release` | Passed |
48
+ | `npm run test:collision` | Passed |
49
+ | `npm run test:routing:skills` | Passed |
50
+ | `npm run test:routing:invariants` | Passed |
51
+ | `npm run test:plugin:openai` | Passed |
52
+ | `npm run test:plugin:submission` | Passed |
53
+ | `npm run test:platform:compatibility` | Passed on rerun |
54
+
55
+ The gates were invoked through `npm.cmd` in PowerShell because this host blocks the `npm.ps1` shim by execution policy. Waza was not installed, so optional Waza checks were unavailable.
@@ -0,0 +1,88 @@
1
+ {
2
+ "format": "harness-codex-live-observation-evidence-v1",
3
+ "testedAt": "2026-10-13",
4
+ "source": {
5
+ "pr": 305,
6
+ "commit": "3f57b8b376bfd437c8cff6ddbe4ddbf036380198",
7
+ "runtimeVersion": "0.30.9",
8
+ "runtimeSha256": "7f4e7801d9fdb7350072b01c91034cd28890dfe7190d143ec7760c85fd5a29df"
9
+ },
10
+ "host": {
11
+ "product": "Codex CLI",
12
+ "version": "0.162.1",
13
+ "os": "win32 10.0.26200 x64",
14
+ "node": "v24.18.0"
15
+ },
16
+ "capture": {
17
+ "path": "user-hook compatibility fallback",
18
+ "trustedHandlers": [
19
+ "UserPromptSubmit: Recording a local System One turn observation",
20
+ "Stop: Recording a local System One turn observation"
21
+ ],
22
+ "trustBypass": false,
23
+ "rawPromptCommitted": false,
24
+ "rawSessionCommitted": false
25
+ },
26
+ "sessionId": "01a1338c-914c-7bf0-b893-3b5e538a129d",
27
+ "turns": [
28
+ {
29
+ "recordId": "09ba6878d736e3f7183e7c3a",
30
+ "host": "codex",
31
+ "observedAt": "2026-10-13T04:37:02.348Z",
32
+ "turn": 1,
33
+ "turnId": "01a1338c-9727-7743-824d-92f8ceb51f00",
34
+ "selfReport": {
35
+ "v": 1,
36
+ "validity": "actionable",
37
+ "contextDependent": false,
38
+ "tier": "tier1",
39
+ "intents": [
40
+ "test"
41
+ ],
42
+ "workflow": null,
43
+ "skills": []
44
+ },
45
+ "selfReportReason": null,
46
+ "behavior": {
47
+ "toolCalls": 0,
48
+ "toolFailures": 0,
49
+ "filesWritten": 0,
50
+ "filesCreated": 0
51
+ }
52
+ },
53
+ {
54
+ "recordId": "81a4c3a35f7894653aaf9f64",
55
+ "host": "codex",
56
+ "observedAt": "2026-10-13T04:40:07.308Z",
57
+ "turn": 2,
58
+ "turnId": "01a1338f-6ea2-7671-997f-e9a205b96102",
59
+ "selfReport": {
60
+ "v": 1,
61
+ "validity": "actionable",
62
+ "contextDependent": false,
63
+ "tier": "tier1",
64
+ "intents": [
65
+ "discuss"
66
+ ],
67
+ "workflow": null,
68
+ "skills": []
69
+ },
70
+ "selfReportReason": null,
71
+ "behavior": {
72
+ "toolCalls": 0,
73
+ "toolFailures": 0,
74
+ "filesWritten": 0,
75
+ "filesCreated": 0
76
+ }
77
+ }
78
+ ],
79
+ "assertions": {
80
+ "oneRecordPerTurn": true,
81
+ "bothHostCodex": true,
82
+ "sameSession": true,
83
+ "sequentialTurns": true,
84
+ "distinctTurnIds": true,
85
+ "currentTurnLabelsMatch": true,
86
+ "promptContentOmitted": true
87
+ }
88
+ }
@@ -190,6 +190,7 @@ for (const rel of OPENCODE_SURFACES) {
190
190
 
191
191
  const matrix = JSON.parse(read('docs/platform-compatibility.json'));
192
192
  const opencode = matrix.platforms.find(platform => platform.id === 'opencode');
193
+ const codex = matrix.platforms.find(platform => platform.id === 'codex');
193
194
  for (const dimension of ['hooksLifecycle', 'liveHostVerification']) {
194
195
  if (opencode?.capabilities[dimension]?.status !== 'Partial') {
195
196
  fail(`OpenCode ${dimension}: must remain Partial`);
@@ -198,7 +199,16 @@ for (const dimension of ['hooksLifecycle', 'liveHostVerification']) {
198
199
  if (opencode?.capabilities.liveHostVerification.evidence !== OPENCODE_EVIDENCE) {
199
200
  fail('OpenCode liveHostVerification: missing exact retained artifact path');
200
201
  }
201
- for (const platform of matrix.platforms.filter(platform => platform.id !== 'opencode')) {
202
+ if (codex?.capabilities.hooksLifecycle.status !== 'Partial') {
203
+ fail('Codex hooksLifecycle: expected partial live verification for the compatibility fallback');
204
+ }
205
+ if (codex?.capabilities.liveHostVerification.status !== 'Partial') {
206
+ fail('Codex liveHostVerification: expected partial status with preserved evidence');
207
+ }
208
+ if (codex?.capabilities.liveHostVerification.evidence !== 'benchmarks/results/live-host/codex-2026-10-13/README.md') {
209
+ fail('Codex liveHostVerification: missing exact retained artifact path');
210
+ }
211
+ for (const platform of matrix.platforms.filter(platform => !['opencode', 'codex'].includes(platform.id))) {
202
212
  if (platform.capabilities.liveHostVerification.status !== 'Unknown') {
203
213
  fail(`${platform.id}: liveHostVerification must remain Unknown without its own evidence`);
204
214
  }
@@ -1,5 +1,6 @@
1
1
  """Stub scorer behind the real resident server code; never model-quality evidence."""
2
2
  import importlib.util
3
+ import json
3
4
  from pathlib import Path
4
5
  import sys
5
6
  import time
@@ -28,4 +29,9 @@ def factory(weights, manifest):
28
29
  if __name__ == '__main__':
29
30
  if sys.argv[1:2] != ['--serve']:
30
31
  sys.exit(2)
31
- sys.exit(adapter.serve_main(sys.argv[2:], scorer_factory=factory))
32
+ result = adapter.serve_main(sys.argv[2:], scorer_factory=factory)
33
+ # Deterministic teardown fixture: the state/socket are closed, but PID is
34
+ # deliberately alive to distinguish state unlink from full process exit.
35
+ if json.loads(sys.argv[2]).get('modelId') == 'stub-exit-linger':
36
+ time.sleep(0.5)
37
+ sys.exit(result)
@@ -73,6 +73,8 @@ try {
73
73
  const state = compat.status({ codexHome });
74
74
  check(state.installed && state.healthy && state.exactEntriesPresent === state.expectedEntries,
75
75
  'status verifies exact installed entry hashes and runtime tree');
76
+ check(state.observationCapture.status === 'configured-unverified' && state.observationCapture.trustRequired,
77
+ 'status distinguishes installed hooks from Codex-trusted live capture');
76
78
 
77
79
  const config = read(hooksFile);
78
80
  check(config.description === original.description &&
@@ -158,6 +160,18 @@ try {
158
160
  check(!fs.existsSync(path.join(emptyHome, 'hooks.json')),
159
161
  'uninstall removes hooks.json only when Harness created it and no user entries remain');
160
162
 
163
+ const marketplaceSource = path.join(root, 'marketplace-plugin');
164
+ const marketplaceHome = path.join(root, 'marketplace-codex');
165
+ fs.cpSync(compat.SOURCE_PLUGIN_ROOT, marketplaceSource, { recursive: true });
166
+ fs.writeFileSync(path.join(marketplaceSource, 'hooks', 'marketplace-source-marker.txt'), 'newer marketplace source\n');
167
+ const marketplaceInstall = compat.install({ codexHome: marketplaceHome, sourcePluginRoot: marketplaceSource });
168
+ const marketplaceManifest = read(path.join(marketplaceHome, 'harness-everything', 'compat-hooks', 'manifest.json'));
169
+ check(marketplaceInstall.sourcePluginSha256 === compat.treeHash(marketplaceSource) &&
170
+ marketplaceManifest.sourcePluginSha256 === compat.treeHash(marketplaceSource) &&
171
+ fs.readFileSync(path.join(marketplaceManifest.runtimeDir, 'hooks', 'marketplace-source-marker.txt'), 'utf8') === 'newer marketplace source\n',
172
+ 'compatibility runtime is copied from the verified installed marketplace source');
173
+ compat.uninstall({ codexHome: marketplaceHome });
174
+
161
175
  const duplicateHome = path.join(root, 'duplicate-codex');
162
176
  fs.mkdirSync(duplicateHome, { recursive: true });
163
177
  compat.install({ codexHome: duplicateHome });
@@ -273,6 +287,11 @@ try {
273
287
  check(malformedRefused && fs.readFileSync(malformed, 'utf8') === malformedBefore,
274
288
  'malformed user hooks.json fails closed without replacement');
275
289
 
290
+ const inactive = compat.status({ codexHome: path.join(root, 'not-installed') });
291
+ check(inactive.observationCapture.status === 'inactive' &&
292
+ inactive.observationCapture.reason === 'compatibility-hooks-not-installed',
293
+ 'status clearly reports inactive observation capture when hooks are absent');
294
+
276
295
  const source = read(path.join(compat.SOURCE_PLUGIN_ROOT, 'hooks', 'hooks.json'));
277
296
  const sourceCount = Object.values(source.hooks).reduce((sum, entries) => sum + entries.length, 0);
278
297
  check(sourceCount === installed.entries,
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('assert');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const { spawnSync } = require('child_process');
9
+
10
+ const ROOT = path.resolve(__dirname, '..');
11
+ const IMPORTER = path.join(ROOT, 'scripts', 'system-one-observations-import-codex-sessions.js');
12
+ const EXPORTER = path.join(ROOT, 'scripts', 'system-one-observations-export.js');
13
+ const REVIEW = path.join(ROOT, 'scripts', 'system-one-observations-review.js');
14
+ const obs = require('../hooks/scripts/lib/observations');
15
+ const LABEL = '<!-- harness-label {"v":1,"validity":"actionable","contextDependent":false,"tier":"tier2","intents":["fix"],"workflow":null,"skills":[]} -->';
16
+ const TIER1_LABEL = '<!-- harness-label {"v":1,"validity":"actionable","contextDependent":false,"tier":"tier1","intents":["fix"],"workflow":null,"skills":[]} -->';
17
+
18
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-session-label-import-'));
19
+ const sessions = path.join(root, 'sessions');
20
+ const store = path.join(root, 'observations');
21
+ fs.mkdirSync(path.join(sessions, '2026', '10'), { recursive: true });
22
+ const rollout = path.join(sessions, '2026', '10', 'rollout-fixture.jsonl');
23
+
24
+ const rows = [
25
+ { type: 'session_meta', payload: { session_id: 'fixture-session' } },
26
+ { type: 'event_msg', timestamp: '2026-09-26T23:59:59.000Z', payload: { type: 'task_started', turn_id: 'old', started_at: '2026-09-26T23:59:59.000Z' } },
27
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'pre-revision prompt' }] } },
28
+ { type: 'event_msg', timestamp: '2026-09-26T23:59:59.500Z', payload: { type: 'task_complete', turn_id: 'old', last_agent_message: LABEL } },
29
+ { type: 'event_msg', timestamp: '2026-09-27T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'included', started_at: '2026-09-27T00:00:00.000Z' } },
30
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [
31
+ { type: 'input_text', text: 'Please fix the parser. Contact user@example.com.' },
32
+ { type: 'input_text', text: 'injected host context must stay out of the prompt row' }
33
+ ] } },
34
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'later context message' }] } },
35
+ { type: 'event_msg', timestamp: '2026-09-27T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'included', last_agent_message: 'Done.\n' + LABEL } },
36
+ { type: 'event_msg', timestamp: '2026-09-30T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'assistant-fallback', started_at: '2026-09-30T00:00:00.000Z' } },
37
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'fallback prompt from the rollout message' }] } },
38
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'Completed answer.\n' + LABEL }] } },
39
+ { type: 'event_msg', timestamp: '2026-09-30T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'assistant-fallback' } },
40
+ { type: 'event_msg', timestamp: '2026-09-30T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'explicit-unlabeled-final', started_at: '2026-09-30T00:00:00.000Z' } },
41
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'an earlier assistant message had a label' }] } },
42
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'commentary', content: [{ type: 'output_text', text: 'Earlier answer.\n' + TIER1_LABEL }] } },
43
+ { type: 'event_msg', timestamp: '2026-09-30T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'explicit-unlabeled-final', last_agent_message: 'This is the explicit final reply without a label.' } },
44
+ { type: 'event_msg', timestamp: '2026-09-30T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'unobserved-write', started_at: '2026-09-30T00:00:00.000Z' } },
45
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'write a small parser change' }] } },
46
+ { type: 'response_item', payload: { type: 'function_call', name: 'apply_patch', arguments: '*** Begin Patch\n*** Add File: parser.js\n+module.exports = true;\n*** End Patch' } },
47
+ { type: 'event_msg', timestamp: '2026-09-30T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'unobserved-write', last_agent_message: TIER1_LABEL } },
48
+ { type: 'event_msg', timestamp: '2026-09-30T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'not-final-assistant', started_at: '2026-09-30T00:00:00.000Z' } },
49
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'do not use an earlier assistant message label' }] } },
50
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: LABEL }] } },
51
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'A later assistant message replaced this one.' }] } },
52
+ { type: 'event_msg', timestamp: '2026-09-30T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'not-final-assistant', last_agent_message: 'No label in the final assistant message.' } },
53
+ { type: 'event_msg', timestamp: '2026-09-28T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'missing-prompt', started_at: '2026-09-28T00:00:00.000Z' } },
54
+ { type: 'event_msg', timestamp: '2026-09-28T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'missing-prompt', last_agent_message: LABEL } },
55
+ { type: 'event_msg', timestamp: '2026-09-29T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'invalid-label', started_at: '2026-09-29T00:00:00.000Z' } },
56
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'malformed label prompt' }] } },
57
+ { type: 'event_msg', timestamp: '2026-09-29T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'invalid-label', last_agent_message: '<!-- harness-label {bad json} -->' } },
58
+ { type: 'event_msg', timestamp: '2026-09-30T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'ambiguous-summary', started_at: '2026-09-30T00:00:00.000Z' } },
59
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'resolve conflicting final representations' }] } },
60
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'final_answer', content: [{ type: 'output_text', text: 'Actual final answer.\n' + LABEL }] } },
61
+ { type: 'event_msg', timestamp: '2026-09-30T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'ambiguous-summary', last_agent_message: 'The completion summary omitted the final response text.' } },
62
+ { type: 'event_msg', timestamp: '2026-09-30T00:02:00.000Z', payload: { type: 'task_started', turn_id: 'after-ambiguous', started_at: '2026-09-30T00:02:00.000Z' } },
63
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'continue after ambiguous final representations' }] } },
64
+ { type: 'event_msg', timestamp: '2026-09-30T00:03:00.000Z', payload: { type: 'task_complete', turn_id: 'after-ambiguous', last_agent_message: 'Follow-up answer.\n' + LABEL } },
65
+ { type: 'event_msg', timestamp: '2026-09-30T00:04:00.000Z', payload: { type: 'task_started', turn_id: 'equivalent-body', started_at: '2026-09-30T00:04:00.000Z' } },
66
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'recover an equivalent assistant body' }] } },
67
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'final_answer', content: [{ type: 'output_text', text: 'Equivalent final answer.\n' + LABEL }] } },
68
+ { type: 'event_msg', timestamp: '2026-09-30T00:05:00.000Z', payload: { type: 'task_complete', turn_id: 'equivalent-body', last_agent_message: 'Equivalent final answer.' } },
69
+ { type: 'event_msg', timestamp: '2026-09-30T00:06:00.000Z', payload: { type: 'task_started', turn_id: 'nonstring-completion', started_at: '2026-09-30T00:06:00.000Z' } },
70
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'fallback from non-string completion' }] } },
71
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'Non-string fallback answer.\n' + LABEL }] } },
72
+ { type: 'event_msg', timestamp: '2026-09-30T00:07:00.000Z', payload: { type: 'task_complete', turn_id: 'nonstring-completion', last_agent_message: null } },
73
+ { type: 'event_msg', timestamp: '2026-09-30T00:08:00.000Z', payload: { type: 'task_started', turn_id: 'invalid-with-assistant-label', started_at: '2026-09-30T00:08:00.000Z' } },
74
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'reject malformed completion despite assistant label' }] } },
75
+ { type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'final_answer', content: [{ type: 'output_text', text: 'A different assistant body.\n' + LABEL }] } },
76
+ { type: 'event_msg', timestamp: '2026-09-30T00:09:00.000Z', payload: { type: 'task_complete', turn_id: 'invalid-with-assistant-label', last_agent_message: '<!-- harness-label {bad json} -->' } },
77
+ ];
78
+ fs.writeFileSync(rollout, rows.map(row => JSON.stringify(row)).join('\n') + '\n');
79
+
80
+ function runImport(targetStore = store) {
81
+ return spawnSync(process.execPath, [IMPORTER, '--sessions', sessions, '--store', targetStore, '--since', '2026-09-27'], {
82
+ cwd: ROOT,
83
+ encoding: 'utf8',
84
+ timeout: 10000,
85
+ });
86
+ }
87
+
88
+ try {
89
+ const hookEnv = { HARNESS_OBSERVATIONS: 'on', HARNESS_OBSERVATIONS_DIR: store };
90
+ obs.handle({ hook_event_name: 'UserPromptSubmit', session_id: 'fixture-session', turn_id: 'included',
91
+ prompt: 'Please fix the parser. Contact user@example.com.', cwd: root }, hookEnv);
92
+ obs.handle({ hook_event_name: 'PostToolUse', session_id: 'fixture-session', turn_id: 'included', cwd: root,
93
+ tool_name: 'apply_patch', tool_input: { patch: '*** Begin Patch\n*** Add File: parser.js\n+module.exports = true;\n*** End Patch' } }, hookEnv);
94
+ obs.handle({ hook_event_name: 'Stop', session_id: 'fixture-session', turn_id: 'included', last_agent_message: TIER1_LABEL }, hookEnv);
95
+
96
+ const first = runImport();
97
+ assert.strictEqual(first.status, 0, 'session import failed:\n' + first.stdout + '\n' + first.stderr);
98
+ const report = JSON.parse(first.stdout);
99
+ assert.strictEqual(report.imported, 5);
100
+ assert.strictEqual(report.skipped.beforeCutoff, 1);
101
+ assert.strictEqual(report.skipped.missingPrompt, 1);
102
+ assert.strictEqual(report.skipped.missingLabel, 2, 'an explicit unlabeled completion remains a missing-label skip when the labeled assistant item was commentary');
103
+ assert.strictEqual(report.skipped.ambiguousFinalMessage, 1, 'conflicting final representations with a labeled final assistant item are reported as ambiguous');
104
+ assert.strictEqual(report.skipped.invalidLabel, 2, 'an invalid completion label remains invalid even when an assistant item has a valid label');
105
+ assert.strictEqual(report.skipped.duplicate, 1, 'backfill skips a turn already captured by live Codex hooks');
106
+ assert.strictEqual(report.skipped.ambiguousLiveMatch, 0);
107
+
108
+ const index = JSON.parse(fs.readFileSync(path.join(store, 'observations-index.json'), 'utf8'));
109
+ assert.strictEqual(index.records.length, 6);
110
+ const liveRecord = index.records.find(entry => entry.writer && entry.writer.turnId === 'included');
111
+ assert(liveRecord, 'live records retain Codex turn IDs for cross-source dedupe');
112
+ assert.strictEqual(liveRecord.behavior.filesWritten, 1, 'the live fixture contains an observed file write');
113
+ assert.deepStrictEqual(obs.contradictions(liveRecord.selfReport, liveRecord.behavior), ['tier1-with-writes']);
114
+ assert.strictEqual(index.records.filter(entry => entry.writer && entry.writer.sessionId === 'fixture-session' && entry.writer.turnId === 'included').length, 1,
115
+ 'live capture and session backfill leave one record for a turn');
116
+
117
+ const fallbackRecord = index.records.find(entry => entry.captureMethod === 'session-backfill' && entry.turn === 3);
118
+ assert(fallbackRecord, 'the response_item assistant label is recovered when task_complete omits it');
119
+ const fallbackText = JSON.parse(fs.readFileSync(path.join(store, 'text', fallbackRecord.contentSha256 + '.json'), 'utf8'));
120
+ assert.strictEqual(fallbackText.prompt, 'fallback prompt from the rollout message');
121
+ const backfilledTier1 = index.records.find(entry => entry.captureMethod === 'session-backfill' && entry.selfReport.tier === 'tier1');
122
+ assert(backfilledTier1, 'the tier1 label from the rollout is imported');
123
+ assert.strictEqual(backfilledTier1.behavior, null, 'session backfill records behavior as unobserved rather than zero');
124
+ assert(!index.records.some(entry => entry.writer && entry.writer.turnId === 'ambiguous-summary'),
125
+ 'a differing completion string must not inherit the assistant message label');
126
+ const afterAmbiguous = index.records.find(entry => entry.writer && entry.writer.turnId === 'after-ambiguous');
127
+ assert(afterAmbiguous, 'the turn after an ambiguous final can still be imported');
128
+ const afterAmbiguousText = JSON.parse(fs.readFileSync(path.join(store, 'text', afterAmbiguous.contentSha256 + '.json'), 'utf8'));
129
+ assert.strictEqual(afterAmbiguousText.previous, '', 'an unresolved final disagreement must not seed later previous-message context');
130
+ assert(index.records.some(entry => entry.writer && entry.writer.turnId === 'equivalent-body'),
131
+ 'a completion string matching the assistant body after label removal safely recovers its label');
132
+ assert(index.records.some(entry => entry.writer && entry.writer.turnId === 'nonstring-completion'),
133
+ 'a non-string task_complete field falls back to the final assistant label');
134
+ assert(!index.records.some(entry => entry.writer && entry.writer.turnId === 'invalid-with-assistant-label'),
135
+ 'a valid assistant label never replaces an invalid completion label');
136
+
137
+ const exportDir = path.join(root, 'export');
138
+ const exported = spawnSync(process.execPath, [EXPORTER, '--store', store, '--out', exportDir, '--self-report-only'], { cwd: ROOT, encoding: 'utf8', timeout: 10000 });
139
+ assert.strictEqual(exported.status, 0, 'export after backfill failed:\n' + exported.stdout + '\n' + exported.stderr);
140
+ const labels = fs.readFileSync(path.join(exportDir, 'labels-observed.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
141
+ const exportReport = JSON.parse(fs.readFileSync(path.join(exportDir, 'export-report.json'), 'utf8'));
142
+ assert.strictEqual(labels.filter(label => label.behaviorEvidence === 'unobserved').length, 5);
143
+ assert.deepStrictEqual(exportReport.behaviorEvidence, { observed: 1, unobserved: 5 });
144
+ assert.strictEqual(exportReport.contradictions['tier1-with-writes'], 1, 'only the observed live write contributes a contradiction');
145
+
146
+ const reviewHtml = path.join(root, 'review.html');
147
+ const reviewed = spawnSync(process.execPath, [REVIEW, '--store', store, '--out', reviewHtml], { cwd: ROOT, encoding: 'utf8', timeout: 10000 });
148
+ assert.strictEqual(reviewed.status, 0, 'observation review page failed:\n' + reviewed.stdout + '\n' + reviewed.stderr);
149
+ assert.match(fs.readFileSync(reviewHtml, 'utf8'), /Behavior evidence is unobserved/);
150
+
151
+ const legacyStore = path.join(root, 'legacy-observations');
152
+ fs.mkdirSync(legacyStore, { recursive: true });
153
+ fs.writeFileSync(path.join(legacyStore, 'observations-index.json'), JSON.stringify({ schemaVersion: 1, records: [
154
+ {
155
+ id: 'legacy-live-record', source: 'observation', host: 'codex', turn: 2,
156
+ writer: { sessionId: 'fixture-session' }, contentSha256: 'legacy-content-hash', behavior: { filesWritten: 1 }
157
+ },
158
+ {
159
+ id: 'old-backfill-record', source: 'observation', captureMethod: 'session-backfill', host: 'codex', turn: 1,
160
+ writer: { sessionId: 'older-session', turnId: 'old' }, behavior: { filesWritten: 0, toolCalls: 0 }
161
+ }
162
+ ] }));
163
+ const legacy = runImport(legacyStore);
164
+ assert.strictEqual(legacy.status, 0, legacy.stderr);
165
+ const legacyReport = JSON.parse(legacy.stdout);
166
+ assert.strictEqual(legacyReport.skipped.ambiguousLiveMatch, 1,
167
+ 'legacy live records without a turn ID are explicitly skipped when the same session/turn is ambiguous');
168
+ assert.strictEqual(legacyReport.migratedUnobservedBehavior, 1, 'prior backfill records are migrated away from synthetic zero behavior');
169
+ const legacyIndex = JSON.parse(fs.readFileSync(path.join(legacyStore, 'observations-index.json'), 'utf8'));
170
+ assert.strictEqual(legacyIndex.records.find(entry => entry.id === 'old-backfill-record').behavior, null);
171
+
172
+ const second = runImport();
173
+ assert.strictEqual(second.status, 0, second.stderr);
174
+ assert.strictEqual(JSON.parse(second.stdout).imported, 0, 'repeated imports must not duplicate records');
175
+ assert.strictEqual(JSON.parse(second.stdout).skipped.duplicate, 6);
176
+ assert.strictEqual(JSON.parse(fs.readFileSync(path.join(store, 'observations-index.json'), 'utf8')).records.length, 6);
177
+
178
+ const lockSessions = path.join(root, 'lock-timeout-sessions');
179
+ fs.mkdirSync(lockSessions, { recursive: true });
180
+ const lockRollout = path.join(lockSessions, 'lock-timeout.jsonl');
181
+ fs.writeFileSync(lockRollout, [
182
+ { type: 'event_msg', timestamp: '2026-10-01T00:00:00.000Z', payload: { type: 'task_started', turn_id: 'lock-timeout', started_at: '2026-10-01T00:00:00.000Z' } },
183
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'a unique turn that cannot be persisted while locked' }] } },
184
+ { type: 'event_msg', timestamp: '2026-10-01T00:01:00.000Z', payload: { type: 'task_complete', turn_id: 'lock-timeout', last_agent_message: LABEL } },
185
+ ].map(row => JSON.stringify(row)).join('\n') + '\n');
186
+ const lockStore = path.join(root, 'lock-timeout-store');
187
+ fs.mkdirSync(lockStore, { recursive: true });
188
+ fs.writeFileSync(path.join(lockStore, '.index.lock'), 'held for importer timeout fixture');
189
+ const locked = spawnSync(process.execPath, [IMPORTER, '--sessions', lockSessions, '--store', lockStore, '--since', '2026-09-27'], {
190
+ cwd: ROOT,
191
+ encoding: 'utf8',
192
+ timeout: 10000,
193
+ });
194
+ assert.strictEqual(locked.status, 1, 'an importer lock timeout must return a failing CLI exit code');
195
+ const lockReport = JSON.parse(locked.stdout);
196
+ assert.strictEqual(lockReport.failed.lockTimeout, 1, 'the report distinguishes a lock timeout from a duplicate');
197
+ assert.strictEqual(fs.existsSync(path.join(lockStore, 'observations-index.json')), false,
198
+ 'a unique turn must not be reported as persisted when the index lock times out');
199
+
200
+ const migrationStore = path.join(root, 'migration-timeout-store');
201
+ fs.mkdirSync(migrationStore, { recursive: true });
202
+ fs.writeFileSync(path.join(migrationStore, 'observations-index.json'), JSON.stringify({ schemaVersion: 1, records: [
203
+ { id: 'old-backfill', captureMethod: 'session-backfill', behavior: { filesWritten: 0 } }
204
+ ] }));
205
+ fs.writeFileSync(path.join(migrationStore, '.index.lock'), 'held during migration fixture');
206
+ const migrationLocked = spawnSync(process.execPath, [IMPORTER, '--sessions', lockSessions, '--store', migrationStore, '--since', '2026-09-27'], {
207
+ cwd: ROOT,
208
+ encoding: 'utf8',
209
+ timeout: 10000,
210
+ });
211
+ assert.strictEqual(migrationLocked.status, 1, 'a migration lock timeout must also fail the importer CLI');
212
+ assert.strictEqual(JSON.parse(migrationLocked.stdout).failed.lockTimeout, 1,
213
+ 'the migration path reports its lock timeout through the same failure count');
214
+ const migrationIndex = JSON.parse(fs.readFileSync(path.join(migrationStore, 'observations-index.json'), 'utf8'));
215
+ assert.deepStrictEqual(migrationIndex.records[0].behavior, { filesWritten: 0 },
216
+ 'a failed migration does not rewrite the index or proceed with imports');
217
+ } finally {
218
+ fs.rmSync(root, { recursive: true, force: true });
219
+ }
220
+
221
+ console.log('PASS: post-revision Codex session labels import as redacted, prompt-paired, idempotent observations.');