harness-everything 0.46.0 → 0.46.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.
@@ -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.46.0",
13
+ "version": "0.46.1",
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.46.0",
4
+ "version": "0.46.1",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.46.1](https://github.com/dyphn1/Harness-everything/compare/v0.46.0...v0.46.1) (2026-10-28)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **doc-reconciliation:** correlate outline dispatch by observed agent result ([c63e1ef](https://github.com/dyphn1/Harness-everything/commit/c63e1efa299496283bffb1019983b0dd54595f64))
10
+
5
11
  ## [0.46.0](https://github.com/dyphn1/Harness-everything/compare/v0.45.0...v0.46.0) (2026-10-28)
6
12
 
7
13
  ### Features
@@ -45,6 +51,7 @@ All notable changes to this project are documented in this file.
45
51
 
46
52
  ### Fixed
47
53
 
54
+ - **doc-reconciliation:** correlate outline dispatches through completed Agent/Task results and unique worker start/stop events while keeping live credit gated.
48
55
  - **doc-governance:** allow regular governed-content revisions and expand base-owned governed/derived globs against Git trees, while retaining review for membership, structural, policy, trust-surface, fixture, and derived-output changes; run the guard in its own CI job.
49
56
 
50
57
  ## [0.43.0](https://github.com/dyphn1/Harness-everything/compare/v0.42.2...v0.43.0) (2026-10-27)
@@ -0,0 +1,75 @@
1
+ # Live-host capture: claude-341-outline-dispatch (2026-10-28)
2
+
3
+ **Verdict: the outline live gate stays UNMET.** Claude Code 2.1.312 carries the `HARNESS_OUTLINE_DISPATCH_V1` prompt, the coordinator `session_id` and a synchronous PreToolUse boundary. It does not carry two fields that `outlineCaptureMappingValid` requires: `SubagentStart.parent_tool_use_id` and an `interrupted` field on the Agent PostToolUse. This is field-availability evidence, not live-host credit. `hooks/scripts/lib/doc-reconciliation-qualification.json` is unchanged, so positive outline credit stays disabled.
4
+
5
+ ## What was captured
6
+
7
+ ```mermaid
8
+ sequenceDiagram
9
+ participant C as Coordinator (session_id)
10
+ participant H as PreToolUse hook (held 2 s)
11
+ participant W as Worker (agent_id)
12
+ C->>H: PreToolUse Agent, tool_input.prompt = marker
13
+ H-->>C: hook ends (+2000 ms)
14
+ C->>W: SubagentStart (+30–100 ms after hook end)<br/>no parent_tool_use_id
15
+ W-->>C: SubagentStop
16
+ C->>C: PostToolUse Agent, tool_use_id matches Pre<br/>agent id only in tool_response.agentId, no interrupted
17
+ ```
18
+
19
+ ## Pins
20
+
21
+ | Item | Value |
22
+ |---|---|
23
+ | Harness HEAD (staged) | `f1e6ab4e45fefa908c06f4f6fe96750b731987f9` (main after #387), clean tree |
24
+ | Staging | `node scripts/stage-claude-plugin.js <scratch>/plugin` (1788 tracked files) |
25
+ | Host | Claude Code `2.1.312`, macOS (darwin 25.6.0), zsh, `--model haiku` |
26
+ | Pilot | `HARNESS_DOC_RECONCILIATION_PILOT=1`, fresh `HARNESS_STATE_HOME` per run |
27
+
28
+ | File | sha256 |
29
+ |---|---|
30
+ | `capture.sanitized.jsonl` (this dir) | `cf1ea56c3d329edcb0a18fc6437af9d9aedf56c76a829f3949de382d07c5a657` |
31
+ | `capture-settings.json` (session `--settings`: PreToolUse/PostToolUse `Agent\|Task`, SubagentStart, SubagentStop) | `1c3081a86a0b1e0aa894f2f09a7884ca9bf5dbf55a08ab84074d8a318bd9c587` |
32
+ | `wrapper.js` (log-only, holds PreToolUse 2 s, runs no Harness hook) | `9071ab6b482fc31b1753df242fcf5983f1dd75ce6c5acf8ba78b1a5921f6b2fe` |
33
+ | staged `hooks/hooks.json` | `aca70167da0fa792e34fb55e2904e2935c83f8f087de9e3cd130dd536b01eafd` |
34
+ | staged `hooks/scripts/lib/doc-reconciliation-provider.js` | `1e0a9cc6fd040c074d96a79ec6445c328957951a7c0074427687d0a22179ec3b` |
35
+
36
+ ## Command (sanitized)
37
+
38
+ ```text
39
+ cd <scratch>/project # fresh git repo, one commit
40
+ HARNESS_DOC_RECONCILIATION_PILOT=1 HARNESS_STATE_HOME=<scratch>/state \
41
+ claude -p "<fixed prompt: dispatch outline-worker twice via Agent; 1 = no isolation, 2 = isolation worktree; each prompt = marker line + 'Reply with: worker done'>" \
42
+ --model haiku --plugin-dir <scratch>/plugin --settings <scratch>/capture-settings.json \
43
+ --agents '{"outline-worker":{"description":"Capture worker that replies once and uses no tools","prompt":"Use no tools. Reply exactly: worker done"}}' \
44
+ --allowedTools Agent Task --session-id <fresh UUID> --setting-sources project,local \
45
+ --output-format stream-json --verbose
46
+ ```
47
+
48
+ No bypass flag. Marker: `HARNESS_OUTLINE_DISPATCH_V1 taskId=<id> attemptId=a1 token=<token-n> workerRoot=<scratch>/project`. Both runs exited `0`.
49
+
50
+ - **Run 1** used `taskId=cap-00N`. The plugin rejected it as `dispatch-marker-invalid`, because task ids must match `task-*`. This was a fixture error. The raw host payloads are still valid field evidence.
51
+ - **Run 2** used `taskId=task-cap-00N`. The plugin parsed the marker and recorded `workflow-task-binding-invalid`. That rejection is expected, since no accepted review, handoff or lease exists in this bounded fixture.
52
+
53
+ ## Must-report table (mapping in `outlineCaptureMappingValid`)
54
+
55
+ | Mapping clause | 2.1.312 observed | Evidence |
56
+ |---|---|---|
57
+ | `promptField: tool_input.prompt` | **yes**, verbatim marker | PreToolUse Agent `tool_input.prompt`, 4/4 dispatches |
58
+ | `coordinatorPrincipalField: session_id` | **yes**, but the worker's SubagentStart/Stop carry the **same** `session_id` | Coordinator and worker differ only by `agent_id` presence |
59
+ | `synchronousPreToolUse` | **yes** | 4/4: SubagentStart 30–100 ms after the 2 s PreToolUse hold ended |
60
+ | `workerStartDispatchField: parent_tool_use_id` | **no**, the field is absent | 0/4 SubagentStart payloads. Only `session_id`, `prompt_id` (shared by both dispatches), `agent_id`, `agent_type`, `cwd` |
61
+ | `workerStartRootField: cwd` | **partial** | No isolation: `cwd` = coordinator cwd. `isolation: worktree`: `cwd` = `<project>/.claude/worktrees/agent-<agent_id>`, which is unknown before dispatch, so it cannot equal the marker's `workerRoot` |
62
+ | `taskResultDispatchField: tool_use_id` | **yes** | PostToolUse `tool_use_id` equals the Pre's, 4/4 |
63
+ | Result `agent_id` + `interrupted === false` | **no** | No top-level `agent_id` (the plugin journal lifts `tool_response.agentId`). No `interrupted` field on the Agent result at all |
64
+
65
+ ## Extra finding: async launch
66
+
67
+ In Run 2 dispatch 1 the model omitted `run_in_background`. The host launched the worker asynchronously: PostToolUse `status: async_launched`, `isAsync: true`, and it fired **before** SubagentStop (+2040 ms vs +3490 ms). A PostToolUse is therefore not proof that the worker finished. Any result correlation must require `status: completed` or an explicit `run_in_background: false`.
68
+
69
+ ## What would unblock the gate (owner/advisor decision, not done here)
70
+
71
+ - Correlate worker start without `parent_tool_use_id`: for example, by the order of Pre end → next SubagentStart within the same `session_id` while one dispatch is in flight, or by `tool_response.agentId` ↔ SubagentStart `agent_id` after the fact.
72
+ - Replace `interrupted === false` with `tool_response.status === 'completed'`.
73
+ - Define worker-root for isolation worktrees, where the path is derived from `agent_id`, not predeclared.
74
+
75
+ Each of these changes the mapping in `doc-reconciliation-provider.js`, so it needs advisor adjudication against answer-10 item 2/3 before any implementation.
@@ -0,0 +1,32 @@
1
+ {"run":"run1","phase":"start","ms":0,"event":"PreToolUse","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PreToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture worker dispatch 1","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=cap-001 attemptId=a1 token=<token-1> workerRoot=<scratch>/project\nReply with: worker done","subagent_type":"outline-worker","run_in_background":false},"tool_use_id":"<tool-use-1>"}}
2
+ {"run":"run1","phase":"end","ms":2002,"event":"PreToolUse","tool_use_id":"<tool-use-1>"}
3
+ {"run":"run1","phase":"start","ms":2032,"event":"SubagentStart","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","agent_id":"<agent-1>","agent_type":"outline-worker","hook_event_name":"SubagentStart"}}
4
+ {"run":"run1","phase":"end","ms":2033,"event":"SubagentStart","tool_use_id":null}
5
+ {"run":"run1","phase":"start","ms":3307,"event":"SubagentStop","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-worker","effort":{"level":"medium"},"hook_event_name":"SubagentStop","stop_hook_active":false,"last_assistant_message":"worker done","background_tasks":[],"session_crons":[]}}
6
+ {"run":"run1","phase":"end","ms":3308,"event":"SubagentStop","tool_use_id":null}
7
+ {"run":"run1","phase":"start","ms":3360,"event":"PostToolUse","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture worker dispatch 1","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=cap-001 attemptId=a1 token=<token-1> workerRoot=<scratch>/project\nReply with: worker done","subagent_type":"outline-worker","run_in_background":false},"tool_response":{"keys":["agentId","agentType","canContinueAgent","content","harnessNoteCount","harnessSectionHash","harnessTailCount","prompt","resolvedModel","status","totalDurationMs","totalTokens","totalToolUseCount","usage"],"status":"completed","agentId":"<agent-1>"},"tool_use_id":"<tool-use-1>","duration_ms":1327}}
8
+ {"run":"run1","phase":"end","ms":3361,"event":"PostToolUse","tool_use_id":"<tool-use-1>"}
9
+ {"run":"run1","phase":"start","ms":6198,"event":"PreToolUse","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PreToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture worker dispatch 2","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=cap-002 attemptId=a1 token=<token-2> workerRoot=<scratch>/project\nReply with: worker done","subagent_type":"outline-worker","run_in_background":false,"isolation":"worktree"},"tool_use_id":"<tool-use-2>"}}
10
+ {"run":"run1","phase":"end","ms":8199,"event":"PreToolUse","tool_use_id":"<tool-use-2>"}
11
+ {"run":"run1","phase":"start","ms":8299,"event":"SubagentStart","payload":{"session_id":"<session-1>","cwd":"<scratch>/project/.claude/worktrees/agent-<agent-2>","prompt_id":"<prompt-1>","agent_id":"<agent-2>","agent_type":"outline-worker","hook_event_name":"SubagentStart"}}
12
+ {"run":"run1","phase":"end","ms":8300,"event":"SubagentStart","tool_use_id":null}
13
+ {"run":"run1","phase":"start","ms":9757,"event":"SubagentStop","payload":{"session_id":"<session-1>","cwd":"<scratch>/project/.claude/worktrees/agent-<agent-2>","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-worker","effort":{"level":"medium"},"hook_event_name":"SubagentStop","stop_hook_active":false,"last_assistant_message":"worker done","background_tasks":[],"session_crons":[]}}
14
+ {"run":"run1","phase":"end","ms":9758,"event":"SubagentStop","tool_use_id":null}
15
+ {"run":"run1","phase":"start","ms":9864,"event":"PostToolUse","payload":{"session_id":"<session-1>","cwd":"<scratch>/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture worker dispatch 2","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=cap-002 attemptId=a1 token=<token-2> workerRoot=<scratch>/project\nReply with: worker done","subagent_type":"outline-worker","run_in_background":false,"isolation":"worktree"},"tool_response":{"keys":["agentId","agentType","canContinueAgent","content","harnessNoteCount","harnessSectionHash","harnessTailCount","prompt","resolvedModel","status","totalDurationMs","totalTokens","totalToolUseCount","usage"],"status":"completed","agentId":"<agent-2>"},"tool_use_id":"<tool-use-2>","duration_ms":1635}}
16
+ {"run":"run1","phase":"end","ms":9865,"event":"PostToolUse","tool_use_id":"<tool-use-2>"}
17
+ {"run":"run2","phase":"start","ms":0,"event":"PreToolUse","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-2>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PreToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture dispatch task-cap-001","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-cap-001 attemptId=a1 token=<token-3> workerRoot=<scratch>/project","subagent_type":"outline-worker"},"tool_use_id":"<tool-use-3>"}}
18
+ {"run":"run2","phase":"end","ms":2001,"event":"PreToolUse","tool_use_id":"<tool-use-3>"}
19
+ {"run":"run2","phase":"start","ms":2039,"event":"SubagentStart","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-2>","agent_id":"<agent-3>","agent_type":"outline-worker","hook_event_name":"SubagentStart"}}
20
+ {"run":"run2","phase":"start","ms":2040,"event":"PostToolUse","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-2>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture dispatch task-cap-001","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-cap-001 attemptId=a1 token=<token-3> workerRoot=<scratch>/project","subagent_type":"outline-worker"},"tool_response":{"keys":["agentId","canContinueAgent","canReadOutputFile","description","isAsync","outputFile","prompt","resolvedModel","status"],"status":"async_launched","isAsync":true,"agentId":"<agent-3>"},"tool_use_id":"<tool-use-3>","duration_ms":3}}
21
+ {"run":"run2","phase":"end","ms":2041,"event":"SubagentStart","tool_use_id":null}
22
+ {"run":"run2","phase":"end","ms":2042,"event":"PostToolUse","tool_use_id":"<tool-use-3>"}
23
+ {"run":"run2","phase":"start","ms":3490,"event":"SubagentStop","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-2>","permission_mode":"default","agent_id":"<agent-3>","agent_type":"outline-worker","effort":{"level":"medium"},"hook_event_name":"SubagentStop","stop_hook_active":false,"last_assistant_message":"worker done","background_tasks":[{"id":"<agent-3>","type":"subagent","status":"running","description":"Outline capture dispatch task-cap-001","agent_type":"outline-worker"}],"session_crons":[]}}
24
+ {"run":"run2","phase":"end","ms":3492,"event":"SubagentStop","tool_use_id":null}
25
+ {"run":"run2","phase":"start","ms":7846,"event":"PreToolUse","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-3>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PreToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture dispatch task-cap-002","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-cap-002 attemptId=a1 token=<token-4> workerRoot=<scratch>/project","subagent_type":"outline-worker","run_in_background":false,"isolation":"worktree"},"tool_use_id":"<tool-use-4>"}}
26
+ {"run":"run2","phase":"end","ms":9848,"event":"PreToolUse","tool_use_id":"<tool-use-4>"}
27
+ {"run":"run2","phase":"start","ms":9942,"event":"SubagentStart","payload":{"session_id":"<session-2>","cwd":"<scratch>/project/.claude/worktrees/agent-<agent-4>","prompt_id":"<prompt-3>","agent_id":"<agent-4>","agent_type":"outline-worker","hook_event_name":"SubagentStart"}}
28
+ {"run":"run2","phase":"end","ms":9943,"event":"SubagentStart","tool_use_id":null}
29
+ {"run":"run2","phase":"start","ms":11161,"event":"SubagentStop","payload":{"session_id":"<session-2>","cwd":"<scratch>/project/.claude/worktrees/agent-<agent-4>","prompt_id":"<prompt-3>","permission_mode":"default","agent_id":"<agent-4>","agent_type":"outline-worker","effort":{"level":"medium"},"hook_event_name":"SubagentStop","stop_hook_active":false,"last_assistant_message":"worker done","background_tasks":[],"session_crons":[]}}
30
+ {"run":"run2","phase":"end","ms":11162,"event":"SubagentStop","tool_use_id":null}
31
+ {"run":"run2","phase":"start","ms":11263,"event":"PostToolUse","payload":{"session_id":"<session-2>","cwd":"<scratch>/project","prompt_id":"<prompt-3>","permission_mode":"default","effort":{"level":"medium"},"hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"description":"Outline capture dispatch task-cap-002","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-cap-002 attemptId=a1 token=<token-4> workerRoot=<scratch>/project","subagent_type":"outline-worker","run_in_background":false,"isolation":"worktree"},"tool_response":{"keys":["agentId","agentType","canContinueAgent","content","harnessNoteCount","harnessSectionHash","harnessTailCount","prompt","resolvedModel","status","totalDurationMs","totalTokens","totalToolUseCount","usage"],"status":"completed","agentId":"<agent-4>"},"tool_use_id":"<tool-use-4>","duration_ms":1388}}
32
+ {"run":"run2","phase":"end","ms":11264,"event":"PostToolUse","tool_use_id":"<tool-use-4>"}
@@ -188,7 +188,7 @@ function createOutlineFixture(options = {}) {
188
188
  const reviewResultPayload = {
189
189
  session_id: sessionId, cwd: root, hook_event_name: 'PostToolUse', tool_name: 'Task',
190
190
  tool_use_id: 'review-dispatch', tool_input: { command: 'Task' },
191
- tool_response: { agentId: reviewerPrincipal, interrupted: false },
191
+ tool_response: { agentId: reviewerPrincipal, interrupted: false, status: 'completed' },
192
192
  };
193
193
  const reviewStopPayload = {
194
194
  session_id: sessionId, cwd: root, hook_event_name: 'SubagentStop', agent_id: reviewerPrincipal,
@@ -239,7 +239,7 @@ function createOutlineFixture(options = {}) {
239
239
 
240
240
  const payload = {
241
241
  session_id: sessionId, cwd: root, hook_event_name: 'PreToolUse', tool_name: 'Task',
242
- tool_use_id: options.toolUseId || 'dispatch-1', tool_input: { prompt: marker },
242
+ tool_use_id: options.toolUseId || 'dispatch-1', tool_input: { prompt: marker, run_in_background: false },
243
243
  };
244
244
  if (options.blockSeamStorage) {
245
245
  fs.mkdirSync(path.join(attemptDir, 'seams', `${sha(String(payload.tool_use_id))}.json`), { recursive: true });
@@ -281,7 +281,7 @@ function createOutlineFixture(options = {}) {
281
281
  if (seam) capture.outlineDispatch = seam;
282
282
  const workerStartPayload = {
283
283
  session_id: sessionId, cwd: options.workerStartRoot || root, hook_event_name: 'SubagentStart',
284
- agent_id: options.workerPrincipal || 'draft-worker', agent_type: 'worker', parent_tool_use_id: capture?.tool_use_id || payload.tool_use_id,
284
+ agent_id: options.workerPrincipal || 'draft-worker', agent_type: 'worker',
285
285
  };
286
286
  let workerStart;
287
287
  if (options.realHook) {
@@ -295,7 +295,7 @@ function createOutlineFixture(options = {}) {
295
295
  const extraCaptures = [];
296
296
 
297
297
  function dispatchAgain({ toolUseId = capture.tool_use_id, prompt = payload.tool_input.prompt } = {}) {
298
- const extraPayload = { ...payload, tool_use_id: toolUseId, tool_input: { prompt } };
298
+ const extraPayload = { ...payload, tool_use_id: toolUseId, tool_input: { prompt, run_in_background: false } };
299
299
  const extra = provider.buildCaptureRecord(extraPayload, 'PreToolUse', root);
300
300
  extra.observedIdentity = capture.observedIdentity;
301
301
  if (internals.captureOutlineDispatch) {
@@ -309,7 +309,7 @@ function createOutlineFixture(options = {}) {
309
309
  }
310
310
 
311
311
  function finishDraft({ draft = true, nonDescendant = false, finalReview = false, baselineRoot = root,
312
- omitWorkerStart = false, omitWorkerResult = false, tamperHandoff = false,
312
+ omitWorkerStart = false, omitWorkerResult = false, omitWorkerStop = false, tamperHandoff = false,
313
313
  providerSessionId = sessionId, providerTaskId = taskId, providerBaseline = { root, commit: baselineCommit },
314
314
  runPilot = false, handAuthoredCapture = false, removeSeam = false, mutateAfterFinalReview = false } = {}) {
315
315
  if (draft) {
@@ -337,7 +337,11 @@ function createOutlineFixture(options = {}) {
337
337
  if (!omitWorkerResult) runHook({
338
338
  session_id: sessionId, cwd: root, hook_event_name: 'PostToolUse', tool_name: 'Task',
339
339
  tool_use_id: capture.tool_use_id, tool_input: { command: 'Task' },
340
- tool_response: { agentId: options.workerPrincipal || 'draft-worker', interrupted: false },
340
+ tool_response: { agentId: options.workerPrincipal || 'draft-worker', interrupted: false, status: 'completed' },
341
+ });
342
+ if (!omitWorkerStop) runHook({
343
+ session_id: sessionId, cwd: root, hook_event_name: 'SubagentStop',
344
+ agent_id: options.workerPrincipal || 'draft-worker', agent_type: 'worker',
341
345
  });
342
346
  }
343
347
  if (finalReview) {
@@ -345,7 +349,7 @@ function createOutlineFixture(options = {}) {
345
349
  const finalResultPayload = {
346
350
  session_id: sessionId, cwd: root, hook_event_name: 'PostToolUse', tool_name: 'Task',
347
351
  tool_use_id: 'final-review-dispatch', tool_input: { command: 'Task' },
348
- tool_response: { agentId: finalReviewer, interrupted: false },
352
+ tool_response: { agentId: finalReviewer, interrupted: false, status: 'completed' },
349
353
  };
350
354
  const finalStopPayload = {
351
355
  session_id: sessionId, cwd: root, hook_event_name: 'SubagentStop', agent_id: finalReviewer,
@@ -374,8 +378,10 @@ function createOutlineFixture(options = {}) {
374
378
  : [...reviewCaptures, capture, ...extraCaptures, ...(!omitWorkerStart ? [workerStart] : []), ...(!omitWorkerResult ? [provider.buildCaptureRecord({
375
379
  session_id: sessionId, cwd: root, hook_event_name: 'PostToolUse', tool_name: 'Task',
376
380
  tool_use_id: capture.tool_use_id, tool_input: { command: 'Task' },
377
- tool_response: { agentId: options.workerPrincipal || 'draft-worker', interrupted: false },
378
- }, 'PostToolUse', root)] : [])];
381
+ tool_response: { agentId: options.workerPrincipal || 'draft-worker', interrupted: false, status: 'completed' },
382
+ }, 'PostToolUse', root)] : []), ...(!omitWorkerStop ? [provider.buildCaptureRecord({
383
+ session_id: sessionId, cwd: root, hook_event_name: 'SubagentStop', agent_id: options.workerPrincipal || 'draft-worker', agent_type: 'worker',
384
+ }, 'SubagentStop', root)] : [])];
379
385
  const qualification = { source: 'fake-test-fixture', faked: true, toolUseIdPopulated: true,
380
386
  agentIdPopulated: true, outlineDispatchCaptureVerified: true };
381
387
  if (removeSeam && capture.outlineDispatch?.seamRef) {
@@ -805,14 +811,14 @@ test('canonical root identity accepts an alias across marker, handoff, lease, an
805
811
  } finally { fixture.cleanup(); }
806
812
  });
807
813
 
808
- test('one-use token and tool correlation allow only an identical same-ID duplicate', () => {
814
+ test('one-use token deduplicates identical seams but replayed tool-use records stay unresolved', () => {
809
815
  assert.strictEqual(typeof internals.captureOutlineDispatch, 'function', 'missing token-consuming seam');
810
816
  const duplicate = createOutlineFixture();
811
817
  try {
812
818
  const same = duplicate.dispatchAgain();
813
819
  assert.strictEqual(same.outlineDispatch.state, 'accepted', JSON.stringify(same.outlineDispatch));
814
820
  assert.strictEqual(same.outlineDispatch.duplicate, true);
815
- assert.strictEqual(duplicate.finishDraft().result.status, 'accepted', 'same ID and same input deduplicate to one seam');
821
+ assert.notStrictEqual(duplicate.finishDraft().result.status, 'accepted', 'a replayed host PreToolUse record is not a unique dispatch anchor');
816
822
  } finally { duplicate.cleanup(); }
817
823
 
818
824
  for (const [name, replay] of [
@@ -949,24 +955,202 @@ test('baseline integrity, callback order, delayed result, stash/restore, and uno
949
955
  } finally { shellWriter.cleanup(); }
950
956
  });
951
957
 
952
- test('live qualification requires same-session dispatch, worker-start root, and result correlation', () => {
958
+ const outlineMappingV2 = {
959
+ version: 'claude-outline-dispatch-mapping/2',
960
+ promptField: 'tool_input.prompt',
961
+ dispatchKey: 'session_id+tool_use_id',
962
+ runInBackgroundField: 'tool_input.run_in_background',
963
+ isolationPresenceField: 'tool_input.isolation',
964
+ resultAgentIdField: 'tool_response.agentId',
965
+ resultStatusField: 'tool_response.status',
966
+ workerStartEvent: 'SubagentStart',
967
+ workerStopEvent: 'SubagentStop',
968
+ workerIdentity: 'session_id+agent_id',
969
+ workerRootField: 'cwd',
970
+ };
971
+
972
+ function qualificationDispatch({ id = 'dispatch-1', promptId = 'shared-prompt', background = false, hasBackground = true,
973
+ isolation, hasIsolation = false, session = 'coordinator' } = {}) {
974
+ const toolInput = { prompt: dispatchLine, ...(hasBackground ? { run_in_background: background } : {}) };
975
+ if (hasIsolation) toolInput.isolation = isolation;
976
+ return { event: 'PreToolUse', tool_name: 'Task', tool_use_id: id, session_id: session, prompt_id: promptId, tool_input: toolInput };
977
+ }
978
+
979
+ function qualificationStart(agentId = 'worker', { session = 'coordinator', cwd = '/tmp/prb-worker', id } = {}) {
980
+ return { event: 'SubagentStart', ...(id ? { tool_use_id: id } : {}), agent_id: agentId, session_id: session, cwd };
981
+ }
982
+
983
+ function qualificationStop(agentId = 'worker', { session = 'coordinator', cwd = '/tmp/prb-worker' } = {}) {
984
+ return { event: 'SubagentStop', agent_id: agentId, session_id: session, cwd };
985
+ }
986
+
987
+ function qualificationResult(id = 'dispatch-1', agentId = 'worker', status = 'completed', { session = 'coordinator', legacyAgentId } = {}) {
988
+ const toolResponse = { agentId };
989
+ if (status !== null) toolResponse.status = status;
990
+ return { event: 'PostToolUse', tool_name: 'Task', tool_use_id: id, session_id: session,
991
+ ...(legacyAgentId !== undefined ? { agent_id: legacyAgentId } : {}), tool_response: toolResponse };
992
+ }
993
+
994
+ function outlineCorrelation(records, anchor = records.find(item => item.event === 'PreToolUse')) {
995
+ return internals.correlateOutlineDispatch(records, anchor, '/tmp/prb-worker');
996
+ }
997
+
998
+ test('live qualification accepts the versioned Claude 2.1.312 dispatch/result/worker field contract', () => {
953
999
  assert.strictEqual(typeof internals.outlineCaptureMappingValid, 'function', 'missing qualification correlation validator');
954
- const mapping = { promptField: 'tool_input.prompt', coordinatorPrincipalField: 'session_id', synchronousPreToolUse: true,
955
- workerStartDispatchField: 'parent_tool_use_id', workerStartRootField: 'cwd', taskResultDispatchField: 'tool_use_id' };
956
- const records = [
957
- { event: 'PreToolUse', tool_name: 'Task', tool_use_id: 'dispatch-1', session_id: 'coordinator',
958
- tool_input: { prompt: 'HARNESS_OUTLINE_DISPATCH_V1 taskId=task-prb attemptId=attempt-1 token=token-1 workerRoot=/tmp/worker' } },
959
- { event: 'SubagentStart', parent_tool_use_id: 'dispatch-1', agent_id: 'worker', session_id: 'coordinator', cwd: '/tmp/worker' },
960
- { event: 'PostToolUse', tool_name: 'Task', tool_use_id: 'dispatch-1', agent_id: 'worker', session_id: 'coordinator', interrupted: false },
961
- ];
962
- assert.strictEqual(internals.outlineCaptureMappingValid(mapping, records), true);
1000
+ assert.deepStrictEqual(internals.OUTLINE_CAPTURE_MAPPING, outlineMappingV2, 'mapping field contract must be explicitly versioned');
1001
+ const pre = qualificationDispatch();
1002
+ const records = [pre, qualificationStart(), qualificationResult(), qualificationStop()];
1003
+ assert.strictEqual(internals.outlineCaptureMappingValid(outlineMappingV2, records), true,
1004
+ 'PreToolUse → completed tool_response.agentId → unique Start/Stop is the 2.1.312 host shape');
963
1005
  const crossSession = records.map(item => ({ ...item }));
964
1006
  crossSession[2].session_id = 'different-session';
965
- assert.strictEqual(internals.outlineCaptureMappingValid(mapping, crossSession), false,
1007
+ assert.strictEqual(internals.outlineCaptureMappingValid(outlineMappingV2, crossSession), false,
966
1008
  'matching tool IDs across different coordinator sessions do not qualify the live mapping');
967
1009
  const wrongRoot = records.map(item => ({ ...item }));
968
1010
  wrongRoot[1].cwd = '/tmp/other-worker';
969
- assert.strictEqual(internals.outlineCaptureMappingValid(mapping, wrongRoot), false);
1011
+ assert.strictEqual(internals.outlineCaptureMappingValid(outlineMappingV2, wrongRoot), false);
1012
+ });
1013
+
1014
+ test('capture record retains only explicit outline result and dispatch fields', () => {
1015
+ const pre = provider.buildCaptureRecord({ session_id: 'coordinator', tool_name: 'Task', tool_use_id: 'explicit',
1016
+ tool_input: { prompt: dispatchLine, run_in_background: false } }, 'PreToolUse', '/tmp/prb-worker');
1017
+ assert.strictEqual(pre.run_in_background, false);
1018
+ assert.strictEqual(pre.isolationSet, false);
1019
+ const omitted = provider.buildCaptureRecord({ session_id: 'coordinator', tool_name: 'Agent', tool_use_id: 'omitted',
1020
+ tool_input: { prompt: dispatchLine } }, 'PreToolUse', '/tmp/prb-worker');
1021
+ assert.strictEqual(Object.hasOwn(omitted, 'run_in_background'), false, 'missing background input stays absent');
1022
+ const isolated = provider.buildCaptureRecord({ session_id: 'coordinator', tool_name: 'Agent', tool_use_id: 'isolated',
1023
+ tool_input: { prompt: dispatchLine, run_in_background: false, isolation: null } }, 'PreToolUse', '/tmp/prb-worker');
1024
+ assert.strictEqual(isolated.isolationSet, true, 'present null isolation is not normalized to absence');
1025
+ const result = provider.buildCaptureRecord({ session_id: 'coordinator', tool_name: 'Task', tool_use_id: 'result',
1026
+ tool_response: { agentId: 'worker', status: 'completed' } }, 'PostToolUse', '/tmp/prb-worker');
1027
+ assert.strictEqual(result.tool_response_status, 'completed');
1028
+ const noStatus = provider.buildCaptureRecord({ session_id: 'coordinator', tool_name: 'Task', tool_use_id: 'no-status',
1029
+ tool_response: { agentId: 'worker' } }, 'PostToolUse', '/tmp/prb-worker');
1030
+ assert.strictEqual(Object.hasOwn(noStatus, 'tool_response_status'), false, 'missing result status stays absent');
1031
+ });
1032
+
1033
+ for (const [name, make, reason] of [
1034
+ ['concurrent same-session dispatches cannot reuse one worker result', () => {
1035
+ const one = qualificationDispatch({ id: 'dispatch-1' });
1036
+ const two = qualificationDispatch({ id: 'dispatch-2' });
1037
+ return { records: [one, two, qualificationStart(), qualificationResult('dispatch-1'),
1038
+ qualificationResult('dispatch-2'), qualificationStop()], anchor: one };
1039
+ }, 'outline-worker-agent-id-replayed'],
1040
+ ['a shared prompt_id cannot replace the target tool_use_id', () => {
1041
+ const one = qualificationDispatch({ id: 'dispatch-1' });
1042
+ const two = qualificationDispatch({ id: 'dispatch-2' });
1043
+ return { records: [one, two, qualificationStart('worker-2'), qualificationResult('dispatch-2', 'worker-2'),
1044
+ qualificationStop('worker-2')], anchor: one };
1045
+ }, 'outline-worker-result-missing-or-ambiguous'],
1046
+ ['reordered Stop-before-Start is unresolved', () => {
1047
+ const pre = qualificationDispatch();
1048
+ return { records: [pre, qualificationStop(), qualificationStart(), qualificationResult()], anchor: pre };
1049
+ }, 'outline-worker-stop-before-start'],
1050
+ ['Start-before-Pre is unresolved', () => {
1051
+ const pre = qualificationDispatch();
1052
+ return { records: [qualificationStart(), pre, qualificationResult(), qualificationStop()], anchor: pre };
1053
+ }, 'outline-worker-start-before-dispatch'],
1054
+ ['missing result is unresolved', () => {
1055
+ const pre = qualificationDispatch();
1056
+ return { records: [pre, qualificationStart(), qualificationStop()], anchor: pre };
1057
+ }, 'outline-worker-result-missing-or-ambiguous'],
1058
+ ['duplicate result is unresolved', () => {
1059
+ const pre = qualificationDispatch();
1060
+ return { records: [pre, qualificationStart(), qualificationResult(), qualificationResult(), qualificationStop()], anchor: pre };
1061
+ }, 'outline-worker-result-missing-or-ambiguous'],
1062
+ ['duplicate worker start is unresolved', () => {
1063
+ const pre = qualificationDispatch();
1064
+ return { records: [pre, qualificationStart(), qualificationStart(), qualificationResult(), qualificationStop()], anchor: pre };
1065
+ }, 'outline-worker-start-missing-or-ambiguous'],
1066
+ ['duplicate worker stop is unresolved', () => {
1067
+ const pre = qualificationDispatch();
1068
+ return { records: [pre, qualificationStart(), qualificationResult(), qualificationStop(), qualificationStop()], anchor: pre };
1069
+ }, 'outline-worker-stop-missing-or-ambiguous'],
1070
+ ['conflicting top-level and observed agentId is unresolved', () => {
1071
+ const pre = qualificationDispatch();
1072
+ return { records: [pre, qualificationStart(), qualificationResult('dispatch-1', 'worker', 'completed', { legacyAgentId: 'other-worker' }),
1073
+ qualificationStop()], anchor: pre };
1074
+ }, 'outline-worker-result-agent-id-conflict'],
1075
+ ['async_launched is unresolved', () => {
1076
+ const pre = qualificationDispatch();
1077
+ return { records: [pre, qualificationStart(), qualificationResult('dispatch-1', 'worker', 'async_launched'), qualificationStop()], anchor: pre };
1078
+ }, 'outline-worker-result-async'],
1079
+ ['absent result status is unresolved', () => {
1080
+ const pre = qualificationDispatch();
1081
+ return { records: [pre, qualificationStart(), qualificationResult('dispatch-1', 'worker', null), qualificationStop()], anchor: pre };
1082
+ }, 'outline-worker-result-status-missing'],
1083
+ ['failed result status is unresolved', () => {
1084
+ const pre = qualificationDispatch();
1085
+ return { records: [pre, qualificationStart(), qualificationResult('dispatch-1', 'worker', 'failed'), qualificationStop()], anchor: pre };
1086
+ }, 'outline-worker-result-failed'],
1087
+ ['cancelled result status is unresolved', () => {
1088
+ const pre = qualificationDispatch();
1089
+ return { records: [pre, qualificationStart(), qualificationResult('dispatch-1', 'worker', 'cancelled'), qualificationStop()], anchor: pre };
1090
+ }, 'outline-worker-result-failed'],
1091
+ ['missing Stop is unresolved', () => {
1092
+ const pre = qualificationDispatch();
1093
+ return { records: [pre, qualificationStart(), qualificationResult()], anchor: pre };
1094
+ }, 'outline-worker-stop-missing-or-ambiguous'],
1095
+ ['worker-root mismatch is unresolved', () => {
1096
+ const pre = qualificationDispatch();
1097
+ return { records: [pre, qualificationStart('worker', { cwd: '/tmp/other-worker' }), qualificationResult(), qualificationStop()], anchor: pre };
1098
+ }, 'outline-worker-root-mismatch'],
1099
+ ['worktree isolation is unresolved', () => {
1100
+ const pre = qualificationDispatch({ hasIsolation: true, isolation: 'worktree' });
1101
+ return { records: [pre, qualificationStart(), qualificationResult(), qualificationStop()], anchor: pre };
1102
+ }, 'outline-dispatch-isolation-unsupported'],
1103
+ ['present null isolation is unresolved', () => {
1104
+ const pre = qualificationDispatch({ hasIsolation: true, isolation: null });
1105
+ return { records: [pre, qualificationStart(), qualificationResult(), qualificationStop()], anchor: pre };
1106
+ }, 'outline-dispatch-isolation-unsupported'],
1107
+ ['omitted run_in_background is unresolved', () => {
1108
+ const pre = qualificationDispatch({ hasBackground: false });
1109
+ return { records: [pre, qualificationStart(), qualificationResult(), qualificationStop()], anchor: pre };
1110
+ }, 'outline-dispatch-background-unobserved'],
1111
+ ['an Agent PostToolUseFailure invalidates a completed result', () => {
1112
+ const pre = qualificationDispatch();
1113
+ return { records: [pre, qualificationStart(), qualificationResult(),
1114
+ { event: 'PostToolUseFailure', tool_name: 'Task', tool_use_id: 'dispatch-1', session_id: 'coordinator' }, qualificationStop()], anchor: pre };
1115
+ }, 'outline-worker-result-failed'],
1116
+ ]) {
1117
+ test(`outline correlation rejects ${name}`, () => {
1118
+ const { records, anchor } = make();
1119
+ const result = outlineCorrelation(records, anchor);
1120
+ assert.deepStrictEqual(result, { state: 'unresolved', reason });
1121
+ });
1122
+ }
1123
+
1124
+ test('runtime resolution accepts Stop after the completed result through the synthetic qualification seam', () => {
1125
+ const fixture = createOutlineFixture();
1126
+ try {
1127
+ const completed = fixture.finishDraft();
1128
+ assert.strictEqual(completed.result.status, 'accepted', JSON.stringify(completed.result));
1129
+ const workerResultIndex = completed.captures.findIndex(item => item.event === 'PostToolUse'
1130
+ && item.tool_use_id === fixture.capture.tool_use_id);
1131
+ const workerStopIndex = completed.captures.findIndex(item => item.event === 'SubagentStop'
1132
+ && item.agent_id === 'draft-worker');
1133
+ assert.ok(workerResultIndex >= 0 && workerStopIndex > workerResultIndex,
1134
+ 'the 2.1.312 field shape allows result before Stop; no Stop-before-result condition is implied');
1135
+ } finally { fixture.cleanup(); }
1136
+ });
1137
+
1138
+ test('production export without an outline mapping keeps outline-live-gate-unmet', () => {
1139
+ const fixture = createOutlineFixture();
1140
+ try {
1141
+ const completed = fixture.finishDraft();
1142
+ const production = provider.createClaudeOriginProvider({
1143
+ hostId: 'claude-code', sessionId: fixture.sessionId, workspaceRoot: fixture.root,
1144
+ workflowId: fixture.workflowId, workflowRevision: fixture.workflowRevision, taskId: fixture.taskId,
1145
+ principals: { author: fixture.sessionId, executors: [] },
1146
+ baseline: { root: fixture.root, commit: fixture.baselineCommit }, captures: completed.captures,
1147
+ sessionDir: fixture.sessionDir, taskBinding: fixture.taskBinding,
1148
+ outlineFixture: { expectedPin: fixture.expectedPin },
1149
+ });
1150
+ const result = production.resolveEvent({ reference: `${fixture.capture.ref}#outline-confirmation`, identity: completed.identity });
1151
+ assert.strictEqual(result.status, 'unavailable');
1152
+ assert.ok(result.diagnostics.some(item => item.code === 'outline-live-gate-unmet'), JSON.stringify(result));
1153
+ } finally { fixture.cleanup(); }
970
1154
  });
971
1155
 
972
1156
  test('original B and immutable attempt chain survive reconfirmation; stale attempts/tokens never cover new scope', () => {
@@ -966,7 +966,7 @@ fields through sanitization; a transcript alone, synthetic payload or screenshot
966
966
  this hook-capture evidence.
967
967
 
968
968
  The origin provider's qualification check MUST resolve that retained evidence reference (or its
969
- verified export), validate captured host/event kinds, nonempty tool/subagent IDs, parent/session
969
+ verified export), validate captured host/event kinds, nonempty tool/subagent IDs, session/tool-use
970
970
  correlation and recorded digests, and match the supported host/capture implementation being used.
971
971
  Missing, malformed, mismatched or unresolvable qualification evidence returns unavailable and no
972
972
  credit. Do not enable qualification from a caller `trusted` flag, environment toggle, populated
@@ -976,6 +976,22 @@ host capture path exposed the fields; every later event still needs current orig
976
976
  A retained capture does not authenticate arbitrary local journals or imply E1 GO. Record the live
977
977
  gate as unmet until this check actually passes; do not label the docs or deterministic tests as live.
978
978
 
979
+ For PR-B outline dispatches, the versioned mapping contract is
980
+ `claude-outline-dispatch-mapping/2`. A Task/Agent `PreToolUse` anchor requires `session_id` plus
981
+ `tool_use_id`, one valid marker in `tool_input.prompt`, explicit
982
+ `tool_input.run_in_background === false`, and no `tool_input.isolation` property. Exactly one
983
+ Task/Agent `PostToolUse` with that key must report `tool_response.status === 'completed'` and a
984
+ nonempty `tool_response.agentId`; that observed ID must match exactly one `SubagentStart` and one
985
+ `SubagentStop` with the same session. Journal order requires PreToolUse before SubagentStart before
986
+ SubagentStop; the completed PostToolUse may appear before or after SubagentStop. The Start and Stop
987
+ must each report a canonical `cwd` equal to the marker's worker root. `prompt_id` is shared across
988
+ dispatches, and neither `parent_tool_use_id` nor `interrupted` is an outline dispatch key or
989
+ completion signal. Captured records retain `tool_response.status` as `tool_response_status`, the
990
+ explicit `run_in_background` value, and whether `isolation` was present; absent status or background
991
+ fields remain absent. Async launches and ambiguous or contradictory matches stay unresolved.
992
+ The retained qualification export still has no outline mapping, so production remains
993
+ `outline-live-gate-unmet` and this contract does not enable positive credit.
994
+
979
995
  ### Workflow credit, review progression and freshness
980
996
 
981
997
  Every ON + supported credit attempt MUST invoke the following current chain, with the same
@@ -1236,10 +1252,10 @@ flowchart LR
1236
1252
  | B and task | Keep the workflow-start B commit/root/tree and ordered requirement IDs for every attempt. Config scope must still match B. An attempt cannot reset B to O. |
1237
1253
  | Inventory | The coordinator supplies exact bytes and `inventory-pin.json` outside the candidate tree. The pinned source is `report-inv.md`, SHA-256 `2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6`, run `e1-inv-1`, input SHA `0ef0541c6cae550e8269ba712e03797e0e987dfb`, approval `#325` comment `6436207729`, decision 1. The provider rehashes the bytes and compares every pin field before resolving one row and domain. Missing, stale, duplicate, ambiguous, wrong-target or unavailable evidence is unresolved with no fallback. Synthetic test pins prove parser/mechanism binding only. |
1238
1254
  | O and review | O is bound to its commit, tree, target blobs and stored-byte digests. Its `proposed` ADR frontmatter cites one `Inventory row: <id>`, target and domain, and contains no completed Decision text. A host-captured `SubagentStop` review must approve those exact O bytes and the pinned inventory, with a distinct host `agent_id` and correlated Agent/Task result. |
1239
- | Handoff and dispatch | One immutable attempt handoff binds B, O, scope, inventory, accepted review, coordinator principal, lease and one-use token. Agent/Task dispatch accepts exactly one `HARNESS_OUTLINE_DISPATCH_V1` block in `tool_input.prompt`, whose token must equal the handoff token; it does not search free-form text or use another field or cwd as a fallback. Before worker start, PreToolUse verifies the handoff, review, pin, exclusive lease, canonical root, HEAD/tree at O and clean index/worktree including untracked files; it creates a write-once handoff-digest seal, stores validation digests/reasons, then consumes the token by exclusive creation. Every predecessor seal, seam and token remains bound to the original handoff bytes, so an overwritten predecessor invalidates the chain. |
1240
- | Worker and D | Worker start must correlate to the dispatch token/tool ID, principal and declared root; the eventual Task/Agent result must name that same worker. D is checked against the current candidate separately from O. O must be a strict ancestor of D. A squash may retain the cycle only when a coordinator correspondence record proves identical target blobs between retained pre-merge D and the merged tree; changed bytes need fresh final quality review. |
1255
+ | Handoff and dispatch | One immutable attempt handoff binds B, O, scope, inventory, accepted review, coordinator principal, lease and one-use token. Agent/Task dispatch accepts exactly one `HARNESS_OUTLINE_DISPATCH_V1` block in `tool_input.prompt`, whose token must equal the handoff token; it does not search free-form text or use another field or cwd as a fallback. The outline anchor also requires explicit `run_in_background === false` and absence of any `isolation` key, including null or empty values. Before worker start, PreToolUse verifies the handoff, review, pin, exclusive lease, canonical root, HEAD/tree at O and clean index/worktree including untracked files; it creates a write-once handoff-digest seal, stores validation digests/reasons, then consumes the token by exclusive creation. Every predecessor seal, seam and token remains bound to the original handoff bytes, so an overwritten predecessor invalidates the chain. |
1256
+ | Worker and D | The dispatch key is `(session_id, tool_use_id)`. Exactly one matching Task/Agent result must carry `tool_response.status === 'completed'`; its observed `tool_response.agentId` must map to exactly one `SubagentStart` and one `SubagentStop` with the same `session_id` and `agent_id`. Require PreToolUse < SubagentStart < SubagentStop in journal order, but do not order the completed result against Stop. Both worker events need exact canonical `cwd === workerRoot`. `prompt_id` and `parent_tool_use_id` are not correlation keys; `interrupted` is not completion proof. Async launches, failures, cancellation, missing/duplicate events, replayed IDs, worktree isolation and root mismatch stay unresolved. D is checked against the current candidate separately from O. O must be a strict ancestor of D. A squash may retain the cycle only when a coordinator correspondence record proves identical target blobs between retained pre-merge D and the merged tree; changed bytes need fresh final quality review. |
1241
1257
  | Checker outcome | `outline-confirmation` has its own pass/unresolved lane. Generic command success and waivers never satisfy it. No D is incomplete/unresolved. Its result is separate from the aggregate result over every MUST derived from original B→D; an outline pass never waives lint, conflict, quality review, requirement-sync or other MUSTs. |
1242
1258
 
1243
- Capture and validation failures stay fail-open to host tools and withhold evidence credit. Unclassified shell commands and writes outside the correlated worker/root remain unresolved. Retries use a fresh attempt, token, clean worktree and immutable handoff; only one non-superseded leaf is current. Exact same-tool-ID/input duplicates may resolve to one seam; changed input or token reuse is unresolved. OpenCode and Codex remain unsupported for positive outline credit.
1259
+ Capture and validation failures stay fail-open to host tools and withhold evidence credit. Unclassified shell commands and writes outside the correlated worker/root remain unresolved. Retries use a fresh attempt, token, clean worktree and immutable handoff; only one non-superseded leaf is current. An identical same-tool-ID/input callback may reuse its stored seam, but repeated PreToolUse records remain ambiguous for evidence credit; changed input or token reuse is unresolved. OpenCode and Codex remain unsupported for positive outline credit.
1244
1260
 
1245
- The current retained Claude qualification does not establish the frozen prompt field, coordinator principal, synchronous PreToolUse boundary or worker-root mapping. The outline provider therefore grants no positive live-host credit until a retained Claude capture validates those mappings. Deterministic fixtures exercise the real hook/provider/adapter/checker/pilot path with a synthetic inventory pin; they are mechanism evidence, not live-host behavior or E1 readiness.
1261
+ The current retained Claude qualification does not establish the versioned `claude-outline-dispatch-mapping/2` contract. The outline provider therefore grants no positive live-host credit until a retained Claude capture validates the prompt, coordinator, result status/agent ID, explicit non-background boundary, isolation absence and exact worker-root mapping. Deterministic fixtures exercise the real hook/provider/adapter/checker/pilot path with a synthetic inventory pin; they are mechanism evidence, not live-host behavior or E1 readiness.
@@ -52,6 +52,19 @@ const DOC_CHECK_BOOLEAN_FLAGS = new Set(['--conflict-assessment']);
52
52
  const DOC_CHECK_SCHEMA = 'harness.doc-check.check.v1';
53
53
  const DOC_CHECK_CONFLICT_SCHEMA = 'harness.doc-check.conflict-assessment.v1';
54
54
  const OUTLINE_DISPATCH_SCHEMA = 'harness.outline-dispatch.v1';
55
+ const OUTLINE_CAPTURE_MAPPING = Object.freeze({
56
+ version: 'claude-outline-dispatch-mapping/2',
57
+ promptField: 'tool_input.prompt',
58
+ dispatchKey: 'session_id+tool_use_id',
59
+ runInBackgroundField: 'tool_input.run_in_background',
60
+ isolationPresenceField: 'tool_input.isolation',
61
+ resultAgentIdField: 'tool_response.agentId',
62
+ resultStatusField: 'tool_response.status',
63
+ workerStartEvent: 'SubagentStart',
64
+ workerStopEvent: 'SubagentStop',
65
+ workerIdentity: 'session_id+agent_id',
66
+ workerRootField: 'cwd',
67
+ });
55
68
  const OUTLINE_SEAM_SCHEMA = 'harness.outline-dispatch-seam.v1';
56
69
  const OUTLINE_HANDOFF_SCHEMA = 'harness.outline-handoff.v1';
57
70
  const OUTLINE_LEASE_SCHEMA = 'harness.outline-writer-lease.v1';
@@ -1163,28 +1176,125 @@ function validateOutlineCaptureQualification(input, expectedHost = HOST_ID) {
1163
1176
  }
1164
1177
 
1165
1178
  function outlineCaptureMappingValid(mapping, records) {
1166
- if (!recordOf(mapping) || mapping.promptField !== 'tool_input.prompt'
1167
- || mapping.coordinatorPrincipalField !== 'session_id' || mapping.synchronousPreToolUse !== true
1168
- || mapping.workerStartDispatchField !== 'parent_tool_use_id' || mapping.workerStartRootField !== 'cwd'
1169
- || mapping.taskResultDispatchField !== 'tool_use_id' || !Array.isArray(records)) return false;
1179
+ if (!recordOf(mapping) || captureJson(mapping) !== captureJson(OUTLINE_CAPTURE_MAPPING) || !Array.isArray(records)) return false;
1170
1180
  const pres = records.filter(item => recordOf(item) && item.event === 'PreToolUse'
1171
1181
  && ['Task', 'Agent'].includes(item.tool_name) && recordOf(item.tool_input)
1172
1182
  && typeof item.tool_input.prompt === 'string' && textOf(item.session_id) && textOf(item.tool_use_id));
1173
- return pres.some(pre => {
1183
+ return pres.some(pre => correlateOutlineDispatch(records, pre).state === 'accepted');
1184
+ }
1185
+
1186
+ function outlineCorrelationFailure(reason) { return { state: 'unresolved', reason }; }
1187
+
1188
+ function outlineDispatchKeyMatches(item, pre) {
1189
+ return recordOf(item) && item.session_id === pre.session_id && item.tool_use_id === pre.tool_use_id;
1190
+ }
1191
+
1192
+ function outlineResultAgentId(result) {
1193
+ if (recordOf(result.tool_response)) {
1194
+ const observed = result.tool_response.agentId;
1195
+ if (textOf(result.agent_id) && textOf(observed) && result.agent_id !== observed) {
1196
+ return { state: 'unresolved', reason: 'outline-worker-result-agent-id-conflict' };
1197
+ }
1198
+ return { state: textOf(observed) ? 'accepted' : 'unresolved', agentId: observed,
1199
+ ...(textOf(observed) ? {} : { reason: 'outline-worker-result-agent-id-missing' }) };
1200
+ }
1201
+ return textOf(result.agent_id)
1202
+ ? { state: 'accepted', agentId: result.agent_id }
1203
+ : { state: 'unresolved', reason: 'outline-worker-result-agent-id-missing' };
1204
+ }
1205
+
1206
+ function outlineResultStatus(result) {
1207
+ if (recordOf(result.tool_response)) return Object.prototype.hasOwnProperty.call(result.tool_response, 'status')
1208
+ ? { present: true, value: result.tool_response.status } : { present: false };
1209
+ return Object.prototype.hasOwnProperty.call(result, 'tool_response_status')
1210
+ ? { present: true, value: result.tool_response_status } : { present: false };
1211
+ }
1212
+
1213
+ function correlateOutlineDispatch(records, anchor, expectedWorkerRoot) {
1214
+ if (!Array.isArray(records)) return outlineCorrelationFailure('outline-capture-records-missing');
1215
+ if (!recordOf(anchor) || anchor.event !== 'PreToolUse' || !['Task', 'Agent'].includes(anchor.tool_name)) {
1216
+ return outlineCorrelationFailure('outline-dispatch-anchor-invalid');
1217
+ }
1218
+ if (!textOf(anchor.session_id) || !textOf(anchor.tool_use_id)) return outlineCorrelationFailure('outline-dispatch-key-missing');
1219
+ const pres = records.filter(item => recordOf(item) && item.event === 'PreToolUse'
1220
+ && ['Task', 'Agent'].includes(item.tool_name) && outlineDispatchKeyMatches(item, anchor));
1221
+ if (pres.length !== 1) return outlineCorrelationFailure('outline-dispatch-anchor-missing-or-ambiguous');
1222
+ const pre = pres[0];
1223
+ let workerRoot = expectedWorkerRoot;
1224
+ if (recordOf(pre.tool_input)) {
1174
1225
  const marker = parseOutlineDispatchPrompt(pre.tool_input.prompt);
1175
- if (!marker) return false;
1176
- const starts = records.filter(item => recordOf(item) && item.event === 'SubagentStart'
1177
- && item.parent_tool_use_id === pre.tool_use_id && item.session_id === pre.session_id
1178
- && textOf(item.cwd) && textOf(marker.workerRoot) && key(item.cwd) === key(marker.workerRoot)
1179
- && textOf(item.agent_id));
1180
- if (starts.length !== 1) return false;
1181
- const worker = starts[0];
1182
- const results = records.filter(item => recordOf(item) && item.event === 'PostToolUse'
1183
- && ['Task', 'Agent'].includes(item.tool_name) && item.tool_use_id === pre.tool_use_id
1184
- && item.session_id === pre.session_id && item.agent_id === worker.agent_id
1185
- && item.interrupted === false);
1186
- return results.length === 1;
1187
- });
1226
+ if (!marker) return outlineCorrelationFailure('outline-dispatch-marker-invalid');
1227
+ if (workerRoot !== undefined && !samePathKey(marker.workerRoot, workerRoot)) {
1228
+ return outlineCorrelationFailure('outline-dispatch-worker-root-mismatch');
1229
+ }
1230
+ workerRoot = marker.workerRoot;
1231
+ if (!Object.prototype.hasOwnProperty.call(pre.tool_input, 'run_in_background')) {
1232
+ return outlineCorrelationFailure('outline-dispatch-background-unobserved');
1233
+ }
1234
+ if (pre.tool_input.run_in_background !== false) {
1235
+ return outlineCorrelationFailure('outline-dispatch-background-enabled');
1236
+ }
1237
+ if (Object.prototype.hasOwnProperty.call(pre.tool_input, 'isolation')) {
1238
+ return outlineCorrelationFailure('outline-dispatch-isolation-unsupported');
1239
+ }
1240
+ } else {
1241
+ if (!recordOf(pre.outlineDispatch) || pre.outlineDispatch.state !== 'accepted' || !textOf(pre.outlineDispatch.workerRoot)) {
1242
+ return outlineCorrelationFailure('outline-dispatch-marker-unobserved');
1243
+ }
1244
+ if (workerRoot !== undefined && !samePathKey(pre.outlineDispatch.workerRoot, workerRoot)) {
1245
+ return outlineCorrelationFailure('outline-dispatch-worker-root-mismatch');
1246
+ }
1247
+ workerRoot = pre.outlineDispatch.workerRoot;
1248
+ if (!Object.prototype.hasOwnProperty.call(pre, 'run_in_background')) {
1249
+ return outlineCorrelationFailure('outline-dispatch-background-unobserved');
1250
+ }
1251
+ if (pre.run_in_background !== false) return outlineCorrelationFailure('outline-dispatch-background-enabled');
1252
+ if (!Object.prototype.hasOwnProperty.call(pre, 'isolationSet')) {
1253
+ return outlineCorrelationFailure('outline-dispatch-isolation-unobserved');
1254
+ }
1255
+ if (pre.isolationSet !== false) return outlineCorrelationFailure('outline-dispatch-isolation-unsupported');
1256
+ }
1257
+ if (!textOf(workerRoot) || !path.isAbsolute(workerRoot)) return outlineCorrelationFailure('outline-dispatch-worker-root-unobserved');
1258
+
1259
+ const failures = records.filter(item => outlineDispatchKeyMatches(item, pre) && item.event === 'PostToolUseFailure');
1260
+ if (failures.length) return outlineCorrelationFailure('outline-worker-result-failed');
1261
+ const results = records.filter(item => outlineDispatchKeyMatches(item, pre) && item.event === 'PostToolUse'
1262
+ && ['Task', 'Agent'].includes(item.tool_name));
1263
+ if (results.length !== 1) return outlineCorrelationFailure('outline-worker-result-missing-or-ambiguous');
1264
+ const result = results[0];
1265
+ const status = outlineResultStatus(result);
1266
+ if (!status.present || status.value === undefined) return outlineCorrelationFailure('outline-worker-result-status-missing');
1267
+ if (status.value === 'async_launched') return outlineCorrelationFailure('outline-worker-result-async');
1268
+ if (status.value !== 'completed') return outlineCorrelationFailure('outline-worker-result-failed');
1269
+ const observedAgent = outlineResultAgentId(result);
1270
+ if (observedAgent.state !== 'accepted') return outlineCorrelationFailure(observedAgent.reason);
1271
+ const agentId = observedAgent.agentId;
1272
+ const sameAgentResults = records.filter(item => recordOf(item) && item.event === 'PostToolUse'
1273
+ && ['Task', 'Agent'].includes(item.tool_name) && item.session_id === pre.session_id
1274
+ && outlineResultAgentId(item).state === 'accepted' && outlineResultAgentId(item).agentId === agentId);
1275
+ if (sameAgentResults.length !== 1) return outlineCorrelationFailure('outline-worker-agent-id-replayed');
1276
+
1277
+ const starts = records.filter(item => recordOf(item) && item.event === 'SubagentStart'
1278
+ && item.session_id === pre.session_id && item.agent_id === agentId);
1279
+ if (starts.length !== 1) return outlineCorrelationFailure('outline-worker-start-missing-or-ambiguous');
1280
+ const stops = records.filter(item => recordOf(item) && item.event === 'SubagentStop'
1281
+ && item.session_id === pre.session_id && item.agent_id === agentId);
1282
+ if (stops.length !== 1) return outlineCorrelationFailure('outline-worker-stop-missing-or-ambiguous');
1283
+ const start = starts[0];
1284
+ const stop = stops[0];
1285
+ const preIndex = records.indexOf(pre);
1286
+ const startIndex = records.indexOf(start);
1287
+ const stopIndex = records.indexOf(stop);
1288
+ if (preIndex < 0 || startIndex < 0 || stopIndex < 0) return outlineCorrelationFailure('outline-worker-journal-position-unavailable');
1289
+ if (startIndex <= preIndex) return outlineCorrelationFailure('outline-worker-start-before-dispatch');
1290
+ if (stopIndex <= startIndex) return outlineCorrelationFailure('outline-worker-stop-before-start');
1291
+ for (const workerEvent of [start, stop]) {
1292
+ if (!textOf(workerEvent.cwd) || !path.isAbsolute(workerEvent.cwd)) {
1293
+ return outlineCorrelationFailure('outline-worker-root-unobserved');
1294
+ }
1295
+ if (!samePathKey(workerEvent.cwd, workerRoot)) return outlineCorrelationFailure('outline-worker-root-mismatch');
1296
+ }
1297
+ return { state: 'accepted', pre, result, start, stop, workerRoot, agentId };
1188
1298
  }
1189
1299
 
1190
1300
  function verifyOutlineMergeCorrespondence({ root, outlineCommit, draftCommit, mergedCommit, targetPaths, record } = {}) {
@@ -1335,20 +1445,10 @@ function resolveOutlineEvidence({ safeInput, capture, captures, request, originR
1335
1445
  || review.reviewDigest !== seam.reviewDigest || review.reviewerPrincipal !== seam.reviewerPrincipal) {
1336
1446
  return fail(review.reason || 'outline-review-changed-after-dispatch');
1337
1447
  }
1338
- const startEvents = (Array.isArray(captures) ? captures : []).filter(item => recordOf(item)
1339
- && item.event === 'SubagentStart' && item.parent_tool_use_id === cap.tool_use_id && item.session_id === cap.session_id);
1340
- if (startEvents.length !== 1 || !textOf(startEvents[0].agent_id)) return fail('outline-worker-start-missing-or-ambiguous');
1341
- const worker = startEvents[0];
1342
- let workerRoot;
1343
- try { workerRoot = canonicalOutlineRoot(worker.cwd); }
1344
- catch { return fail('outline-worker-root-unobserved'); }
1345
- if (!sameCanonicalPath(workerRoot, seam.workerRoot)) return fail('outline-worker-root-mismatch');
1346
- const results = (Array.isArray(captures) ? captures : []).filter(item => recordOf(item)
1347
- && item.event === 'PostToolUse' && ['Task', 'Agent'].includes(item.tool_name)
1348
- && item.tool_use_id === cap.tool_use_id && item.session_id === cap.session_id);
1349
- if (results.length !== 1 || results[0].agent_id !== worker.agent_id || results[0].interrupted !== false) {
1350
- return fail('outline-worker-result-missing-or-mismatched');
1351
- }
1448
+ const correlation = correlateOutlineDispatch(captures, cap, seam.workerRoot);
1449
+ if (correlation.state !== 'accepted') return fail(correlation.reason);
1450
+ const worker = correlation.start;
1451
+ const workerRoot = correlation.workerRoot;
1352
1452
  if (review.reviewerPrincipal === worker.agent_id) return fail('outline-reviewer-is-draft-worker');
1353
1453
  const principals = { author: safeInput.sessionId, executors: journalExecutors(captures, safeInput.sessionId) };
1354
1454
  if (!reviewRecordIndependent(review.review, captures, principals)) return fail('outline-review-not-independent');
@@ -1949,6 +2049,8 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
1949
2049
  if (event === 'PreToolUse' && PRE_CAPTURE_TOOLS.indexOf(payload.tool_name) === -1) return null;
1950
2050
  const toolInput = recordOf(payload.tool_input) ? payload.tool_input : {};
1951
2051
  const toolResponse = payload.tool_response;
2052
+ const outlinePre = event === 'PreToolUse' && ['Task', 'Agent'].includes(payload.tool_name);
2053
+ const outlineResult = event === 'PostToolUse' && ['Task', 'Agent'].includes(payload.tool_name);
1952
2054
  // Demonstrated alias mapping (doc: "Host result aliases such as agentId
1953
2055
  // require a demonstrated capture mapping to the same subagent identity"):
1954
2056
  // the PostToolUse Task/Agent result surface reports the subagent id as
@@ -1961,17 +2063,28 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
1961
2063
  ref: captureId,
1962
2064
  event,
1963
2065
  command: typeof toolInput.command === 'string' ? toolInput.command : '',
1964
- cwd: typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : (typeof workspaceRoot === 'string' && workspaceRoot ? workspaceRoot : ''),
1965
2066
  tool_use_id: typeof payload.tool_use_id === 'string' ? payload.tool_use_id : '',
1966
2067
  session_id: payload.session_id === undefined && payload.sessionId === undefined ? '' : String(payload.session_id !== undefined ? payload.session_id : payload.sessionId),
1967
2068
  responseDigest: captureDigest(toolResponse),
1968
2069
  responseRef: `host-payload/${event}/${captureId}`,
1969
2070
  };
2071
+ if (typeof payload.cwd === 'string' && payload.cwd) record.cwd = payload.cwd;
2072
+ else if (!['SubagentStart', 'SubagentStop'].includes(event) && typeof workspaceRoot === 'string' && workspaceRoot) record.cwd = workspaceRoot;
1970
2073
  if (payload.prompt_id !== undefined) record.prompt_id = payload.prompt_id;
1971
2074
  if (payload.parent_tool_use_id !== undefined) record.parent_tool_use_id = payload.parent_tool_use_id;
1972
2075
  // The launch tool name separates the Agent/Task result surface and the subagent launch from executed tools (F4/F5).
1973
2076
  if (typeof payload.tool_name === 'string') record.tool_name = payload.tool_name;
1974
- const agentId = payload.agent_id !== undefined ? payload.agent_id : responseAliasAgentId;
2077
+ if (outlinePre) {
2078
+ if (Object.prototype.hasOwnProperty.call(toolInput, 'run_in_background')) record.run_in_background = toolInput.run_in_background;
2079
+ record.isolationSet = Object.prototype.hasOwnProperty.call(toolInput, 'isolation');
2080
+ }
2081
+ if (outlineResult && recordOf(toolResponse) && Object.prototype.hasOwnProperty.call(toolResponse, 'status')) {
2082
+ record.tool_response_status = toolResponse.status;
2083
+ }
2084
+ const resultAgentConflict = outlineResult && textOf(payload.agent_id) && textOf(responseAliasAgentId)
2085
+ && payload.agent_id !== responseAliasAgentId;
2086
+ const agentId = outlineResult ? (resultAgentConflict ? undefined : responseAliasAgentId)
2087
+ : payload.agent_id !== undefined ? payload.agent_id : responseAliasAgentId;
1975
2088
  if (agentId !== undefined) {
1976
2089
  record.agent_id = agentId;
1977
2090
  if (payload.agent_type !== undefined) record.agent_type = payload.agent_type;
@@ -2132,4 +2245,4 @@ module.exports = { createClaudeOriginProvider, createOpencodeOriginProvider, bui
2132
2245
  _internals: { captureJson, captureDigest, digestText, canonicalCapture, parseOutlineDispatchPrompt,
2133
2246
  parseInventoryPinRows, validateOutlineInventoryPin, resolveInventoryRow, parseOutlineCitation, resolveOutlineAttempt,
2134
2247
  captureOutlineDispatch, resolveOutlineEvidence, verifyOutlineMergeCorrespondence, validateOutlineCaptureQualification,
2135
- outlineCaptureMappingValid, OUTLINE_INVENTORY_PIN } };
2248
+ outlineCaptureMappingValid, correlateOutlineDispatch, OUTLINE_CAPTURE_MAPPING, OUTLINE_INVENTORY_PIN } };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.46.0",
4
+ "version": "0.46.1",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.46.0",
3
+ "version": "0.46.1",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.46.0",
3
+ "version": "0.46.1",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -52,6 +52,19 @@ const DOC_CHECK_BOOLEAN_FLAGS = new Set(['--conflict-assessment']);
52
52
  const DOC_CHECK_SCHEMA = 'harness.doc-check.check.v1';
53
53
  const DOC_CHECK_CONFLICT_SCHEMA = 'harness.doc-check.conflict-assessment.v1';
54
54
  const OUTLINE_DISPATCH_SCHEMA = 'harness.outline-dispatch.v1';
55
+ const OUTLINE_CAPTURE_MAPPING = Object.freeze({
56
+ version: 'claude-outline-dispatch-mapping/2',
57
+ promptField: 'tool_input.prompt',
58
+ dispatchKey: 'session_id+tool_use_id',
59
+ runInBackgroundField: 'tool_input.run_in_background',
60
+ isolationPresenceField: 'tool_input.isolation',
61
+ resultAgentIdField: 'tool_response.agentId',
62
+ resultStatusField: 'tool_response.status',
63
+ workerStartEvent: 'SubagentStart',
64
+ workerStopEvent: 'SubagentStop',
65
+ workerIdentity: 'session_id+agent_id',
66
+ workerRootField: 'cwd',
67
+ });
55
68
  const OUTLINE_SEAM_SCHEMA = 'harness.outline-dispatch-seam.v1';
56
69
  const OUTLINE_HANDOFF_SCHEMA = 'harness.outline-handoff.v1';
57
70
  const OUTLINE_LEASE_SCHEMA = 'harness.outline-writer-lease.v1';
@@ -1163,28 +1176,125 @@ function validateOutlineCaptureQualification(input, expectedHost = HOST_ID) {
1163
1176
  }
1164
1177
 
1165
1178
  function outlineCaptureMappingValid(mapping, records) {
1166
- if (!recordOf(mapping) || mapping.promptField !== 'tool_input.prompt'
1167
- || mapping.coordinatorPrincipalField !== 'session_id' || mapping.synchronousPreToolUse !== true
1168
- || mapping.workerStartDispatchField !== 'parent_tool_use_id' || mapping.workerStartRootField !== 'cwd'
1169
- || mapping.taskResultDispatchField !== 'tool_use_id' || !Array.isArray(records)) return false;
1179
+ if (!recordOf(mapping) || captureJson(mapping) !== captureJson(OUTLINE_CAPTURE_MAPPING) || !Array.isArray(records)) return false;
1170
1180
  const pres = records.filter(item => recordOf(item) && item.event === 'PreToolUse'
1171
1181
  && ['Task', 'Agent'].includes(item.tool_name) && recordOf(item.tool_input)
1172
1182
  && typeof item.tool_input.prompt === 'string' && textOf(item.session_id) && textOf(item.tool_use_id));
1173
- return pres.some(pre => {
1183
+ return pres.some(pre => correlateOutlineDispatch(records, pre).state === 'accepted');
1184
+ }
1185
+
1186
+ function outlineCorrelationFailure(reason) { return { state: 'unresolved', reason }; }
1187
+
1188
+ function outlineDispatchKeyMatches(item, pre) {
1189
+ return recordOf(item) && item.session_id === pre.session_id && item.tool_use_id === pre.tool_use_id;
1190
+ }
1191
+
1192
+ function outlineResultAgentId(result) {
1193
+ if (recordOf(result.tool_response)) {
1194
+ const observed = result.tool_response.agentId;
1195
+ if (textOf(result.agent_id) && textOf(observed) && result.agent_id !== observed) {
1196
+ return { state: 'unresolved', reason: 'outline-worker-result-agent-id-conflict' };
1197
+ }
1198
+ return { state: textOf(observed) ? 'accepted' : 'unresolved', agentId: observed,
1199
+ ...(textOf(observed) ? {} : { reason: 'outline-worker-result-agent-id-missing' }) };
1200
+ }
1201
+ return textOf(result.agent_id)
1202
+ ? { state: 'accepted', agentId: result.agent_id }
1203
+ : { state: 'unresolved', reason: 'outline-worker-result-agent-id-missing' };
1204
+ }
1205
+
1206
+ function outlineResultStatus(result) {
1207
+ if (recordOf(result.tool_response)) return Object.prototype.hasOwnProperty.call(result.tool_response, 'status')
1208
+ ? { present: true, value: result.tool_response.status } : { present: false };
1209
+ return Object.prototype.hasOwnProperty.call(result, 'tool_response_status')
1210
+ ? { present: true, value: result.tool_response_status } : { present: false };
1211
+ }
1212
+
1213
+ function correlateOutlineDispatch(records, anchor, expectedWorkerRoot) {
1214
+ if (!Array.isArray(records)) return outlineCorrelationFailure('outline-capture-records-missing');
1215
+ if (!recordOf(anchor) || anchor.event !== 'PreToolUse' || !['Task', 'Agent'].includes(anchor.tool_name)) {
1216
+ return outlineCorrelationFailure('outline-dispatch-anchor-invalid');
1217
+ }
1218
+ if (!textOf(anchor.session_id) || !textOf(anchor.tool_use_id)) return outlineCorrelationFailure('outline-dispatch-key-missing');
1219
+ const pres = records.filter(item => recordOf(item) && item.event === 'PreToolUse'
1220
+ && ['Task', 'Agent'].includes(item.tool_name) && outlineDispatchKeyMatches(item, anchor));
1221
+ if (pres.length !== 1) return outlineCorrelationFailure('outline-dispatch-anchor-missing-or-ambiguous');
1222
+ const pre = pres[0];
1223
+ let workerRoot = expectedWorkerRoot;
1224
+ if (recordOf(pre.tool_input)) {
1174
1225
  const marker = parseOutlineDispatchPrompt(pre.tool_input.prompt);
1175
- if (!marker) return false;
1176
- const starts = records.filter(item => recordOf(item) && item.event === 'SubagentStart'
1177
- && item.parent_tool_use_id === pre.tool_use_id && item.session_id === pre.session_id
1178
- && textOf(item.cwd) && textOf(marker.workerRoot) && key(item.cwd) === key(marker.workerRoot)
1179
- && textOf(item.agent_id));
1180
- if (starts.length !== 1) return false;
1181
- const worker = starts[0];
1182
- const results = records.filter(item => recordOf(item) && item.event === 'PostToolUse'
1183
- && ['Task', 'Agent'].includes(item.tool_name) && item.tool_use_id === pre.tool_use_id
1184
- && item.session_id === pre.session_id && item.agent_id === worker.agent_id
1185
- && item.interrupted === false);
1186
- return results.length === 1;
1187
- });
1226
+ if (!marker) return outlineCorrelationFailure('outline-dispatch-marker-invalid');
1227
+ if (workerRoot !== undefined && !samePathKey(marker.workerRoot, workerRoot)) {
1228
+ return outlineCorrelationFailure('outline-dispatch-worker-root-mismatch');
1229
+ }
1230
+ workerRoot = marker.workerRoot;
1231
+ if (!Object.prototype.hasOwnProperty.call(pre.tool_input, 'run_in_background')) {
1232
+ return outlineCorrelationFailure('outline-dispatch-background-unobserved');
1233
+ }
1234
+ if (pre.tool_input.run_in_background !== false) {
1235
+ return outlineCorrelationFailure('outline-dispatch-background-enabled');
1236
+ }
1237
+ if (Object.prototype.hasOwnProperty.call(pre.tool_input, 'isolation')) {
1238
+ return outlineCorrelationFailure('outline-dispatch-isolation-unsupported');
1239
+ }
1240
+ } else {
1241
+ if (!recordOf(pre.outlineDispatch) || pre.outlineDispatch.state !== 'accepted' || !textOf(pre.outlineDispatch.workerRoot)) {
1242
+ return outlineCorrelationFailure('outline-dispatch-marker-unobserved');
1243
+ }
1244
+ if (workerRoot !== undefined && !samePathKey(pre.outlineDispatch.workerRoot, workerRoot)) {
1245
+ return outlineCorrelationFailure('outline-dispatch-worker-root-mismatch');
1246
+ }
1247
+ workerRoot = pre.outlineDispatch.workerRoot;
1248
+ if (!Object.prototype.hasOwnProperty.call(pre, 'run_in_background')) {
1249
+ return outlineCorrelationFailure('outline-dispatch-background-unobserved');
1250
+ }
1251
+ if (pre.run_in_background !== false) return outlineCorrelationFailure('outline-dispatch-background-enabled');
1252
+ if (!Object.prototype.hasOwnProperty.call(pre, 'isolationSet')) {
1253
+ return outlineCorrelationFailure('outline-dispatch-isolation-unobserved');
1254
+ }
1255
+ if (pre.isolationSet !== false) return outlineCorrelationFailure('outline-dispatch-isolation-unsupported');
1256
+ }
1257
+ if (!textOf(workerRoot) || !path.isAbsolute(workerRoot)) return outlineCorrelationFailure('outline-dispatch-worker-root-unobserved');
1258
+
1259
+ const failures = records.filter(item => outlineDispatchKeyMatches(item, pre) && item.event === 'PostToolUseFailure');
1260
+ if (failures.length) return outlineCorrelationFailure('outline-worker-result-failed');
1261
+ const results = records.filter(item => outlineDispatchKeyMatches(item, pre) && item.event === 'PostToolUse'
1262
+ && ['Task', 'Agent'].includes(item.tool_name));
1263
+ if (results.length !== 1) return outlineCorrelationFailure('outline-worker-result-missing-or-ambiguous');
1264
+ const result = results[0];
1265
+ const status = outlineResultStatus(result);
1266
+ if (!status.present || status.value === undefined) return outlineCorrelationFailure('outline-worker-result-status-missing');
1267
+ if (status.value === 'async_launched') return outlineCorrelationFailure('outline-worker-result-async');
1268
+ if (status.value !== 'completed') return outlineCorrelationFailure('outline-worker-result-failed');
1269
+ const observedAgent = outlineResultAgentId(result);
1270
+ if (observedAgent.state !== 'accepted') return outlineCorrelationFailure(observedAgent.reason);
1271
+ const agentId = observedAgent.agentId;
1272
+ const sameAgentResults = records.filter(item => recordOf(item) && item.event === 'PostToolUse'
1273
+ && ['Task', 'Agent'].includes(item.tool_name) && item.session_id === pre.session_id
1274
+ && outlineResultAgentId(item).state === 'accepted' && outlineResultAgentId(item).agentId === agentId);
1275
+ if (sameAgentResults.length !== 1) return outlineCorrelationFailure('outline-worker-agent-id-replayed');
1276
+
1277
+ const starts = records.filter(item => recordOf(item) && item.event === 'SubagentStart'
1278
+ && item.session_id === pre.session_id && item.agent_id === agentId);
1279
+ if (starts.length !== 1) return outlineCorrelationFailure('outline-worker-start-missing-or-ambiguous');
1280
+ const stops = records.filter(item => recordOf(item) && item.event === 'SubagentStop'
1281
+ && item.session_id === pre.session_id && item.agent_id === agentId);
1282
+ if (stops.length !== 1) return outlineCorrelationFailure('outline-worker-stop-missing-or-ambiguous');
1283
+ const start = starts[0];
1284
+ const stop = stops[0];
1285
+ const preIndex = records.indexOf(pre);
1286
+ const startIndex = records.indexOf(start);
1287
+ const stopIndex = records.indexOf(stop);
1288
+ if (preIndex < 0 || startIndex < 0 || stopIndex < 0) return outlineCorrelationFailure('outline-worker-journal-position-unavailable');
1289
+ if (startIndex <= preIndex) return outlineCorrelationFailure('outline-worker-start-before-dispatch');
1290
+ if (stopIndex <= startIndex) return outlineCorrelationFailure('outline-worker-stop-before-start');
1291
+ for (const workerEvent of [start, stop]) {
1292
+ if (!textOf(workerEvent.cwd) || !path.isAbsolute(workerEvent.cwd)) {
1293
+ return outlineCorrelationFailure('outline-worker-root-unobserved');
1294
+ }
1295
+ if (!samePathKey(workerEvent.cwd, workerRoot)) return outlineCorrelationFailure('outline-worker-root-mismatch');
1296
+ }
1297
+ return { state: 'accepted', pre, result, start, stop, workerRoot, agentId };
1188
1298
  }
1189
1299
 
1190
1300
  function verifyOutlineMergeCorrespondence({ root, outlineCommit, draftCommit, mergedCommit, targetPaths, record } = {}) {
@@ -1335,20 +1445,10 @@ function resolveOutlineEvidence({ safeInput, capture, captures, request, originR
1335
1445
  || review.reviewDigest !== seam.reviewDigest || review.reviewerPrincipal !== seam.reviewerPrincipal) {
1336
1446
  return fail(review.reason || 'outline-review-changed-after-dispatch');
1337
1447
  }
1338
- const startEvents = (Array.isArray(captures) ? captures : []).filter(item => recordOf(item)
1339
- && item.event === 'SubagentStart' && item.parent_tool_use_id === cap.tool_use_id && item.session_id === cap.session_id);
1340
- if (startEvents.length !== 1 || !textOf(startEvents[0].agent_id)) return fail('outline-worker-start-missing-or-ambiguous');
1341
- const worker = startEvents[0];
1342
- let workerRoot;
1343
- try { workerRoot = canonicalOutlineRoot(worker.cwd); }
1344
- catch { return fail('outline-worker-root-unobserved'); }
1345
- if (!sameCanonicalPath(workerRoot, seam.workerRoot)) return fail('outline-worker-root-mismatch');
1346
- const results = (Array.isArray(captures) ? captures : []).filter(item => recordOf(item)
1347
- && item.event === 'PostToolUse' && ['Task', 'Agent'].includes(item.tool_name)
1348
- && item.tool_use_id === cap.tool_use_id && item.session_id === cap.session_id);
1349
- if (results.length !== 1 || results[0].agent_id !== worker.agent_id || results[0].interrupted !== false) {
1350
- return fail('outline-worker-result-missing-or-mismatched');
1351
- }
1448
+ const correlation = correlateOutlineDispatch(captures, cap, seam.workerRoot);
1449
+ if (correlation.state !== 'accepted') return fail(correlation.reason);
1450
+ const worker = correlation.start;
1451
+ const workerRoot = correlation.workerRoot;
1352
1452
  if (review.reviewerPrincipal === worker.agent_id) return fail('outline-reviewer-is-draft-worker');
1353
1453
  const principals = { author: safeInput.sessionId, executors: journalExecutors(captures, safeInput.sessionId) };
1354
1454
  if (!reviewRecordIndependent(review.review, captures, principals)) return fail('outline-review-not-independent');
@@ -1949,6 +2049,8 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
1949
2049
  if (event === 'PreToolUse' && PRE_CAPTURE_TOOLS.indexOf(payload.tool_name) === -1) return null;
1950
2050
  const toolInput = recordOf(payload.tool_input) ? payload.tool_input : {};
1951
2051
  const toolResponse = payload.tool_response;
2052
+ const outlinePre = event === 'PreToolUse' && ['Task', 'Agent'].includes(payload.tool_name);
2053
+ const outlineResult = event === 'PostToolUse' && ['Task', 'Agent'].includes(payload.tool_name);
1952
2054
  // Demonstrated alias mapping (doc: "Host result aliases such as agentId
1953
2055
  // require a demonstrated capture mapping to the same subagent identity"):
1954
2056
  // the PostToolUse Task/Agent result surface reports the subagent id as
@@ -1961,17 +2063,28 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
1961
2063
  ref: captureId,
1962
2064
  event,
1963
2065
  command: typeof toolInput.command === 'string' ? toolInput.command : '',
1964
- cwd: typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : (typeof workspaceRoot === 'string' && workspaceRoot ? workspaceRoot : ''),
1965
2066
  tool_use_id: typeof payload.tool_use_id === 'string' ? payload.tool_use_id : '',
1966
2067
  session_id: payload.session_id === undefined && payload.sessionId === undefined ? '' : String(payload.session_id !== undefined ? payload.session_id : payload.sessionId),
1967
2068
  responseDigest: captureDigest(toolResponse),
1968
2069
  responseRef: `host-payload/${event}/${captureId}`,
1969
2070
  };
2071
+ if (typeof payload.cwd === 'string' && payload.cwd) record.cwd = payload.cwd;
2072
+ else if (!['SubagentStart', 'SubagentStop'].includes(event) && typeof workspaceRoot === 'string' && workspaceRoot) record.cwd = workspaceRoot;
1970
2073
  if (payload.prompt_id !== undefined) record.prompt_id = payload.prompt_id;
1971
2074
  if (payload.parent_tool_use_id !== undefined) record.parent_tool_use_id = payload.parent_tool_use_id;
1972
2075
  // The launch tool name separates the Agent/Task result surface and the subagent launch from executed tools (F4/F5).
1973
2076
  if (typeof payload.tool_name === 'string') record.tool_name = payload.tool_name;
1974
- const agentId = payload.agent_id !== undefined ? payload.agent_id : responseAliasAgentId;
2077
+ if (outlinePre) {
2078
+ if (Object.prototype.hasOwnProperty.call(toolInput, 'run_in_background')) record.run_in_background = toolInput.run_in_background;
2079
+ record.isolationSet = Object.prototype.hasOwnProperty.call(toolInput, 'isolation');
2080
+ }
2081
+ if (outlineResult && recordOf(toolResponse) && Object.prototype.hasOwnProperty.call(toolResponse, 'status')) {
2082
+ record.tool_response_status = toolResponse.status;
2083
+ }
2084
+ const resultAgentConflict = outlineResult && textOf(payload.agent_id) && textOf(responseAliasAgentId)
2085
+ && payload.agent_id !== responseAliasAgentId;
2086
+ const agentId = outlineResult ? (resultAgentConflict ? undefined : responseAliasAgentId)
2087
+ : payload.agent_id !== undefined ? payload.agent_id : responseAliasAgentId;
1975
2088
  if (agentId !== undefined) {
1976
2089
  record.agent_id = agentId;
1977
2090
  if (payload.agent_type !== undefined) record.agent_type = payload.agent_type;
@@ -2132,4 +2245,4 @@ module.exports = { createClaudeOriginProvider, createOpencodeOriginProvider, bui
2132
2245
  _internals: { captureJson, captureDigest, digestText, canonicalCapture, parseOutlineDispatchPrompt,
2133
2246
  parseInventoryPinRows, validateOutlineInventoryPin, resolveInventoryRow, parseOutlineCitation, resolveOutlineAttempt,
2134
2247
  captureOutlineDispatch, resolveOutlineEvidence, verifyOutlineMergeCorrespondence, validateOutlineCaptureQualification,
2135
- outlineCaptureMappingValid, OUTLINE_INVENTORY_PIN } };
2248
+ outlineCaptureMappingValid, correlateOutlineDispatch, OUTLINE_CAPTURE_MAPPING, OUTLINE_INVENTORY_PIN } };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "harness-everything",
4
- "version": "0.46.0",
4
+ "version": "0.46.1",
5
5
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
6
6
  "author": {
7
7
  "name": "Miya Daniel",
package/sbom.cdx.json CHANGED
@@ -2,23 +2,23 @@
2
2
  "$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.7",
5
- "serialNumber": "urn:uuid:4e82c3bc-ded1-5f78-8aa6-6dd26488f41f",
5
+ "serialNumber": "urn:uuid:e14b39ac-6cf4-5711-b0c8-03de1649a568",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-10-28T00:05:02.172Z",
8
+ "timestamp": "2026-10-28T02:23:43.789Z",
9
9
  "tools": [
10
10
  {
11
11
  "vendor": "Harness Everything",
12
12
  "name": "generate-sbom.js",
13
- "version": "0.46.0"
13
+ "version": "0.46.1"
14
14
  }
15
15
  ],
16
16
  "component": {
17
17
  "type": "application",
18
- "bom-ref": "pkg:npm/harness-everything@0.46.0",
18
+ "bom-ref": "pkg:npm/harness-everything@0.46.1",
19
19
  "name": "harness-everything",
20
- "version": "0.46.0",
21
- "purl": "pkg:npm/harness-everything@0.46.0"
20
+ "version": "0.46.1",
21
+ "purl": "pkg:npm/harness-everything@0.46.1"
22
22
  }
23
23
  },
24
24
  "components": [
@@ -2033,7 +2033,7 @@
2033
2033
  ]
2034
2034
  },
2035
2035
  {
2036
- "ref": "pkg:npm/harness-everything@0.46.0",
2036
+ "ref": "pkg:npm/harness-everything@0.46.1",
2037
2037
  "dependsOn": [
2038
2038
  "pkg:npm/%40commitlint/cli@19.8.1",
2039
2039
  "pkg:npm/%40commitlint/config-conventional@19.8.1",