harness-everything 0.46.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +30 -1
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/README.md +33 -8
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md +128 -0
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/sanitize-outline-capture.js +105 -0
- package/ci/mechanism-e1-derived-regenerate.test.js +333 -0
- package/ci/mechanism-outline-sanitizer.test.js +79 -0
- package/ci/mechanism-prb-outline-review.test.js +318 -31
- package/docs/doc-workflow-reconciliation.md +12 -8
- package/docs/platform-capabilities.md +1 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-check.js +106 -8
- package/harness-everything/scripts/doc-lifecycle.js +49 -1
- package/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
- package/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
- package/hooks/scripts/lib/doc-reconciliation-provider.js +182 -13
- package/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +182 -13
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +106 -8
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +49 -1
- package/sbom.cdx.json +7 -7
|
@@ -8,6 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const { execFileSync } = require('child_process');
|
|
9
9
|
const { spawnSync } = require('child_process');
|
|
10
10
|
const { canonical } = require(path.resolve(__dirname, '../hooks/scripts/lib/workflow-isolation.js'));
|
|
11
|
+
const { stage: stageQualificationTree } = require('./lib/doc-reconciliation-staged-tree.js');
|
|
11
12
|
|
|
12
13
|
const ROOT = path.resolve(__dirname, '..');
|
|
13
14
|
const provider = require(path.join(ROOT, 'hooks/scripts/lib/doc-reconciliation-provider.js'));
|
|
@@ -76,7 +77,10 @@ function outlineText({ complete = false, decisionPlaceholder = false, duplicateD
|
|
|
76
77
|
|
|
77
78
|
function createOutlineFixture(options = {}) {
|
|
78
79
|
const root = canonical(fs.mkdtempSync(path.join(os.tmpdir(), 'prb-outline-fixture-')));
|
|
79
|
-
const
|
|
80
|
+
const retainedInventory = options.retainedInventory === true;
|
|
81
|
+
const target = options.target || (retainedInventory ? 'docs/adr/EVID-001.md' : 'docs/adr/DOCS-001.md');
|
|
82
|
+
const rowId = options.rowId || (retainedInventory ? '17' : '20');
|
|
83
|
+
const domain = options.domain || (retainedInventory ? 'EVID' : 'DOCS');
|
|
80
84
|
const taskId = options.taskId || 'task-prb';
|
|
81
85
|
const attemptId = options.attemptId || 'attempt-1';
|
|
82
86
|
const sessionId = options.sessionId || 'author-session';
|
|
@@ -85,7 +89,7 @@ function createOutlineFixture(options = {}) {
|
|
|
85
89
|
? `${root}${path.sep}..${path.sep}${path.basename(root)}` : root;
|
|
86
90
|
const workflowId = 'workflow-prb';
|
|
87
91
|
const workflowRevision = 2;
|
|
88
|
-
const config = JSON.stringify({ governed: [{ path: 'docs/adr/*.md', role: 'adr' }], domains: [
|
|
92
|
+
const config = JSON.stringify({ governed: [{ path: 'docs/adr/*.md', role: 'adr' }], domains: [domain], derived: [] }, null, 2) + '\n';
|
|
89
93
|
const configDigest = sha(config);
|
|
90
94
|
fs.mkdirSync(path.join(root, '.agents/harness-everything'), { recursive: true });
|
|
91
95
|
fs.mkdirSync(path.dirname(path.join(root, target)), { recursive: true });
|
|
@@ -110,7 +114,7 @@ function createOutlineFixture(options = {}) {
|
|
|
110
114
|
decisionPlaceholder: options.decisionPlaceholder === true,
|
|
111
115
|
duplicateDecision: options.duplicateDecision === true,
|
|
112
116
|
indentedDecision: options.indentedDecision === true,
|
|
113
|
-
rowId
|
|
117
|
+
rowId, target, domain,
|
|
114
118
|
}));
|
|
115
119
|
fixtureGit(root, ['add', '-A']);
|
|
116
120
|
fixtureGit(root, ['commit', '-q', '-m', 'O outline']);
|
|
@@ -119,16 +123,20 @@ function createOutlineFixture(options = {}) {
|
|
|
119
123
|
const outlineBytes = fs.readFileSync(path.join(root, target));
|
|
120
124
|
const outlineBlob = fixtureGit(root, ['rev-parse', `HEAD:${target}`]);
|
|
121
125
|
|
|
122
|
-
const
|
|
123
|
-
|
|
126
|
+
const inventoryBytes = retainedInventory
|
|
127
|
+
? fs.readFileSync(path.join(ROOT, 'benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md'))
|
|
128
|
+
: Buffer.from(syntheticInventory);
|
|
129
|
+
const pinDigest = sha(inventoryBytes);
|
|
130
|
+
const expectedPin = retainedInventory ? internals.OUTLINE_INVENTORY_PIN : {
|
|
124
131
|
source: 'synthetic://approved-inventory', sha256: pinDigest, runId: 'synthetic-run', inputSha: 'abcdef012345',
|
|
125
132
|
approvalRef: { issue: 325, commentId: 'synthetic-comment', decision: 1 },
|
|
126
133
|
retainedArtifactPath: `inventory/${pinDigest}.md`,
|
|
127
134
|
};
|
|
135
|
+
if (retainedInventory) assert.strictEqual(pinDigest, expectedPin.sha256, 'retained inventory bytes must match the pinned digest');
|
|
128
136
|
const outlineRoot = path.join(sessionDir, 'doc-reconciliation/outline-confirmation');
|
|
129
137
|
const inventoryDir = path.join(outlineRoot, 'inventory');
|
|
130
138
|
fs.mkdirSync(inventoryDir, { recursive: true });
|
|
131
|
-
fs.writeFileSync(path.join(inventoryDir, `${pinDigest}.md`),
|
|
139
|
+
fs.writeFileSync(path.join(inventoryDir, `${pinDigest}.md`), inventoryBytes);
|
|
132
140
|
writeJson(path.join(inventoryDir, 'inventory-pin.json'), expectedPin);
|
|
133
141
|
const requirementIds = ['prb'];
|
|
134
142
|
const workspaceId = 'ws-' + internals.digestText(internals.captureJson({ workspaceRoot: root }));
|
|
@@ -183,7 +191,7 @@ function createOutlineFixture(options = {}) {
|
|
|
183
191
|
'Review verdict: approve',
|
|
184
192
|
`Reviewed artifact: ${target} sha256:${sha(outlineBytes)}`,
|
|
185
193
|
`Reviewed artifact: ${inventoryPath} sha256:${pinDigest}`,
|
|
186
|
-
`Summary: Approved inventory row
|
|
194
|
+
`Summary: Approved inventory row ${rowId} for ${target} in domain ${domain}.`,
|
|
187
195
|
].join('\n');
|
|
188
196
|
const reviewResultPayload = {
|
|
189
197
|
session_id: sessionId, cwd: root, hook_event_name: 'PostToolUse', tool_name: 'Task',
|
|
@@ -229,7 +237,7 @@ function createOutlineFixture(options = {}) {
|
|
|
229
237
|
baseline: { root, commit: baselineCommit, tree: baselineTree, configDigest },
|
|
230
238
|
configDigest, targetPaths: [target], draftWorktreeRoot: workerRoot,
|
|
231
239
|
outline: { commit: outlineCommit, tree: outlineTree, targetBlobs: { [target]: { blobOid: outlineBlob, storedBytesSha256: sha(outlineBytes) } } },
|
|
232
|
-
inventory: { ...expectedPin, rowId
|
|
240
|
+
inventory: { ...expectedPin, rowId, domain, targetPaths: [target] },
|
|
233
241
|
reviewCaptureRef: reviewStop.ref, reviewCaptureDigest: reviewRecordDigest, reviewDigest: reviewStop.reviewDigest,
|
|
234
242
|
coordinatorPrincipal: sessionId, writerLeaseId: lease.id, writerLeaseDigest: leaseDigest,
|
|
235
243
|
draftDispatchToken: options.token || 'token-1',
|
|
@@ -311,9 +319,10 @@ function createOutlineFixture(options = {}) {
|
|
|
311
319
|
function finishDraft({ draft = true, nonDescendant = false, finalReview = false, baselineRoot = root,
|
|
312
320
|
omitWorkerStart = false, omitWorkerResult = false, omitWorkerStop = false, tamperHandoff = false,
|
|
313
321
|
providerSessionId = sessionId, providerTaskId = taskId, providerBaseline = { root, commit: baselineCommit },
|
|
314
|
-
runPilot = false, handAuthoredCapture = false, removeSeam = false, mutateAfterFinalReview = false
|
|
322
|
+
runPilot = false, handAuthoredCapture = false, removeSeam = false, mutateAfterFinalReview = false,
|
|
323
|
+
postWorkerCaptures = [] } = {}) {
|
|
315
324
|
if (draft) {
|
|
316
|
-
fs.writeFileSync(path.join(root, target), `---\nid:
|
|
325
|
+
fs.writeFileSync(path.join(root, target), `---\nid: ${domain}-001\nstatus: proposed\n---\n\n## Decision\nFinal D for row ${rowId}.\n`);
|
|
317
326
|
fixtureGit(root, ['add', '-A']);
|
|
318
327
|
fixtureGit(root, ['commit', '-q', '-m', 'D final draft']);
|
|
319
328
|
}
|
|
@@ -321,7 +330,7 @@ function createOutlineFixture(options = {}) {
|
|
|
321
330
|
fixtureGit(root, ['checkout', '--orphan', 'unrelated']);
|
|
322
331
|
fixtureGit(root, ['rm', '-rf', '.']);
|
|
323
332
|
fs.mkdirSync(path.dirname(path.join(root, target)), { recursive: true });
|
|
324
|
-
fs.writeFileSync(path.join(root, target), `---\nid:
|
|
333
|
+
fs.writeFileSync(path.join(root, target), `---\nid: ${domain}-001\nstatus: proposed\n---\n\n## Decision\nUnrelated D for row ${rowId}.\n`);
|
|
325
334
|
fixtureGit(root, ['add', '-A']);
|
|
326
335
|
fixtureGit(root, ['-c', 'user.name=outline-fixture', '-c', 'user.email=outline-fixture@invalid', 'commit', '-q', '-m', 'unrelated D']);
|
|
327
336
|
}
|
|
@@ -381,8 +390,8 @@ function createOutlineFixture(options = {}) {
|
|
|
381
390
|
tool_response: { agentId: options.workerPrincipal || 'draft-worker', interrupted: false, status: 'completed' },
|
|
382
391
|
}, 'PostToolUse', root)] : []), ...(!omitWorkerStop ? [provider.buildCaptureRecord({
|
|
383
392
|
session_id: sessionId, cwd: root, hook_event_name: 'SubagentStop', agent_id: options.workerPrincipal || 'draft-worker', agent_type: 'worker',
|
|
384
|
-
}, 'SubagentStop', root)] : [])];
|
|
385
|
-
const qualification = { source: 'fake-test-fixture', faked: true, toolUseIdPopulated: true,
|
|
393
|
+
}, 'SubagentStop', root)] : []), ...postWorkerCaptures];
|
|
394
|
+
const qualification = options.noFakeQualification ? undefined : { source: 'fake-test-fixture', faked: true, toolUseIdPopulated: true,
|
|
386
395
|
agentIdPopulated: true, outlineDispatchCaptureVerified: true };
|
|
387
396
|
if (removeSeam && capture.outlineDispatch?.seamRef) {
|
|
388
397
|
fs.rmSync(path.join(sessionDir, capture.outlineDispatch.seamRef), { force: true });
|
|
@@ -396,7 +405,8 @@ function createOutlineFixture(options = {}) {
|
|
|
396
405
|
const origin = provider.createClaudeOriginProvider({
|
|
397
406
|
hostId: 'claude-code', sessionId: providerSessionId, workspaceRoot: root, workflowId, workflowRevision, taskId: providerTaskId,
|
|
398
407
|
principals: { author: providerSessionId, executors: [] }, baseline: providerBaseline,
|
|
399
|
-
captures, sessionDir, taskBinding,
|
|
408
|
+
captures, sessionDir, taskBinding,
|
|
409
|
+
...(qualification ? { qualification, outlineFixture: { expectedPin } } : {}),
|
|
400
410
|
});
|
|
401
411
|
const identity = {
|
|
402
412
|
workspaceId: origin.resolveContext({}).context.workspaceId, sessionId: providerSessionId, workflowId, workflowRevision, taskId: providerTaskId,
|
|
@@ -408,8 +418,8 @@ function createOutlineFixture(options = {}) {
|
|
|
408
418
|
if (runPilot) {
|
|
409
419
|
const previousSwitch = process.env.HARNESS_DOC_RECONCILIATION_PILOT;
|
|
410
420
|
const previousFactory = provider.createClaudeOriginProvider;
|
|
411
|
-
provider.createClaudeOriginProvider = input => previousFactory({ ...input,
|
|
412
|
-
outlineFixture: { expectedPin }, taskBinding });
|
|
421
|
+
provider.createClaudeOriginProvider = input => previousFactory({ ...input,
|
|
422
|
+
...(qualification ? { qualification, outlineFixture: { expectedPin } } : {}), taskBinding });
|
|
413
423
|
process.env.HARNESS_DOC_RECONCILIATION_PILOT = '1';
|
|
414
424
|
const context = { root, sessionId, sessionDir, activeHost: 'claude-code', workflow: {
|
|
415
425
|
workflowId, revision: workflowRevision, strategy: 'iterative-single', state: 'running',
|
|
@@ -518,6 +528,55 @@ test('coordinator-seeded inventory pin requires exact bytes, all fixed fields, a
|
|
|
518
528
|
assert.strictEqual(internals.resolveInventoryRow(multiDomainResult, '20').state, 'unresolved', 'ambiguous domain mapping is unresolved');
|
|
519
529
|
});
|
|
520
530
|
|
|
531
|
+
test('the trusted inventory pin maps only owner-approved VERI rows 15–18 to EVID', () => {
|
|
532
|
+
const pin = internals.OUTLINE_INVENTORY_PIN;
|
|
533
|
+
const bytes = fs.readFileSync(path.join(ROOT,
|
|
534
|
+
'benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md'));
|
|
535
|
+
const mapping = [{ rowIds: ['15', '16', '17', '18'], sourceDomain: 'VERI', domain: 'EVID',
|
|
536
|
+
approvalRef: '../e1-feasibility-2026-10-28/owner-decision-veri-evid.md' }];
|
|
537
|
+
assert.deepStrictEqual(pin.rowDomainMappings, mapping, 'the owner approval is part of the trusted pin');
|
|
538
|
+
const valid = internals.validateOutlineInventoryPin({ bytes, pin, expectedPin: pin });
|
|
539
|
+
assert.strictEqual(valid.state, 'accepted');
|
|
540
|
+
for (const rowId of ['15', '16', '17', '18']) {
|
|
541
|
+
assert.deepStrictEqual(internals.resolveInventoryRow(valid, rowId).row, { rowId, domain: 'EVID' });
|
|
542
|
+
}
|
|
543
|
+
const substituted = { ...pin, rowDomainMappings: [{ ...mapping[0], domain: 'VERI' }] };
|
|
544
|
+
const stale = internals.validateOutlineInventoryPin({ bytes, pin: substituted, expectedPin: pin });
|
|
545
|
+
assert.strictEqual(stale.state, 'unresolved', 'a substituted domain cannot change the trusted mapping');
|
|
546
|
+
assert.strictEqual(stale.reason, 'inventory-pin-mismatch');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('pilot offers structured SubagentStop reviews only and preserves drafter independence rejection', () => {
|
|
550
|
+
const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prb-review-candidates-'));
|
|
551
|
+
const author = 'author-session';
|
|
552
|
+
const draft = provider.buildCaptureRecord({ session_id: author, hook_event_name: 'PostToolUse', tool_name: 'Task',
|
|
553
|
+
tool_use_id: 'draft-dispatch', tool_response: { agentId: 'draft-worker', status: 'completed' } }, 'PostToolUse', '/tmp');
|
|
554
|
+
const ordinary = provider.buildCaptureRecord({ session_id: author, hook_event_name: 'SubagentStop', agent_id: 'draft-worker',
|
|
555
|
+
last_assistant_message: 'Draft complete. The document is ready.' }, 'SubagentStop', '/tmp');
|
|
556
|
+
const reviewResult = provider.buildCaptureRecord({ session_id: author, hook_event_name: 'PostToolUse', tool_name: 'Task',
|
|
557
|
+
tool_use_id: 'review-dispatch', tool_response: { agentId: 'independent-reviewer', status: 'completed' } }, 'PostToolUse', '/tmp');
|
|
558
|
+
const approval = provider.buildCaptureRecord({ session_id: author, hook_event_name: 'SubagentStop', agent_id: 'independent-reviewer',
|
|
559
|
+
last_assistant_message: `Review verdict: approve\nReviewed artifact: docs/adr/EVID-001.md sha256:${'a'.repeat(64)}\nSummary: exact ADR reviewed.` }, 'SubagentStop', '/tmp');
|
|
560
|
+
for (const record of [draft, ordinary, reviewResult, approval]) provider.writeImmutableEvent(sessionDir, record);
|
|
561
|
+
provider.rebuildJournalView(sessionDir);
|
|
562
|
+
const context = { sessionDir, activeHost: 'claude-code', workflow: { docReconciliationTask: { taskId: 'task-review-selection' } } };
|
|
563
|
+
const captures = provider.readCaptureView(sessionDir).records;
|
|
564
|
+
const reviewRefs = pilot._internals.captureReferences(context).filter(ref => ref.endsWith('#quality-review'));
|
|
565
|
+
assert.deepStrictEqual(reviewRefs, [`${approval.ref}#quality-review`], 'ordinary draft completion is not a review candidate');
|
|
566
|
+
assert.strictEqual(provider.reviewRecordIndependent(approval, captures, { author, executors: ['draft-worker'] }), true);
|
|
567
|
+
|
|
568
|
+
const drafterApproval = provider.buildCaptureRecord({ session_id: author, hook_event_name: 'SubagentStop', agent_id: 'draft-worker',
|
|
569
|
+
last_assistant_message: `Review verdict: approve\nReviewed artifact: docs/adr/EVID-001.md sha256:${'a'.repeat(64)}\nSummary: self approval.` }, 'SubagentStop', '/tmp');
|
|
570
|
+
provider.writeImmutableEvent(sessionDir, drafterApproval);
|
|
571
|
+
provider.rebuildJournalView(sessionDir);
|
|
572
|
+
const withDrafterApproval = provider.readCaptureView(sessionDir).records;
|
|
573
|
+
assert.ok(pilot._internals.captureReferences(context).includes(`${drafterApproval.ref}#quality-review`),
|
|
574
|
+
'an explicit approval remains visible to the provider independence check');
|
|
575
|
+
assert.strictEqual(provider.reviewRecordIndependent(drafterApproval, withDrafterApproval,
|
|
576
|
+
{ author, executors: ['draft-worker'] }), false, 'a drafter cannot approve its own work');
|
|
577
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
578
|
+
});
|
|
579
|
+
|
|
521
580
|
test('generic successful execution or waiver cannot pass outline-confirmation', () => {
|
|
522
581
|
const target = 'docs/adr/DOCS-001.md';
|
|
523
582
|
const digest = sha('final D bytes');
|
|
@@ -911,6 +970,114 @@ test('outline evidence binds exact task, B, O blobs, independent review, worker
|
|
|
911
970
|
'a delayed write/edit callback from the coordinator remains visible and blocks ambiguous writer history');
|
|
912
971
|
} finally { delayedWriter.cleanup(); }
|
|
913
972
|
|
|
973
|
+
// Live run 2026-10-28: a non-isolated reviewer starts in O; only a stop journaled before the dispatch clears it.
|
|
974
|
+
const reviewerStart = fixture => provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
975
|
+
hook_event_name: 'SubagentStart', agent_id: 'outline-reviewer', agent_type: 'reviewer' }, 'SubagentStart', fixture.root);
|
|
976
|
+
const stoppedReviewer = createOutlineFixture();
|
|
977
|
+
try {
|
|
978
|
+
stoppedReviewer.reviewCaptures.unshift(reviewerStart(stoppedReviewer));
|
|
979
|
+
const completed = stoppedReviewer.finishDraft();
|
|
980
|
+
assert.strictEqual(completed.result.status, 'accepted',
|
|
981
|
+
`a reviewer that started and stopped in O before the dispatch is not a competing writer: ${JSON.stringify(completed.result)}`);
|
|
982
|
+
} finally { stoppedReviewer.cleanup(); }
|
|
983
|
+
|
|
984
|
+
const postDraftReviewer = createOutlineFixture();
|
|
985
|
+
try {
|
|
986
|
+
const reviewer = 'post-draft-reviewer';
|
|
987
|
+
const captures = [
|
|
988
|
+
provider.buildCaptureRecord({ session_id: postDraftReviewer.sessionId, cwd: postDraftReviewer.root,
|
|
989
|
+
hook_event_name: 'SubagentStart', agent_id: reviewer, agent_type: 'reviewer' }, 'SubagentStart', postDraftReviewer.root),
|
|
990
|
+
provider.buildCaptureRecord({ session_id: postDraftReviewer.sessionId, cwd: postDraftReviewer.root,
|
|
991
|
+
hook_event_name: 'PostToolUse', tool_name: 'Read', tool_use_id: 'post-draft-read', agent_id: reviewer,
|
|
992
|
+
tool_input: { file_path: postDraftReviewer.target }, tool_response: { interrupted: false } }, 'PostToolUse', postDraftReviewer.root),
|
|
993
|
+
provider.buildCaptureRecord({ session_id: postDraftReviewer.sessionId, cwd: postDraftReviewer.root,
|
|
994
|
+
hook_event_name: 'PostToolUse', tool_name: 'Task', tool_use_id: 'post-draft-review',
|
|
995
|
+
tool_response: { agentId: reviewer, status: 'completed' } }, 'PostToolUse', postDraftReviewer.root),
|
|
996
|
+
provider.buildCaptureRecord({ session_id: postDraftReviewer.sessionId, cwd: postDraftReviewer.root,
|
|
997
|
+
hook_event_name: 'SubagentStop', agent_id: reviewer, agent_type: 'reviewer' }, 'SubagentStop', postDraftReviewer.root),
|
|
998
|
+
];
|
|
999
|
+
const completed = postDraftReviewer.finishDraft({ postWorkerCaptures: captures });
|
|
1000
|
+
assert.strictEqual(completed.result.status, 'accepted',
|
|
1001
|
+
`a same-root reviewer starting after the draft worker's Stop does not overlap the draft interval: ${JSON.stringify(completed.result)}`);
|
|
1002
|
+
} finally { postDraftReviewer.cleanup(); }
|
|
1003
|
+
|
|
1004
|
+
for (const [name, arrange] of [
|
|
1005
|
+
['post-stop reviewer missing Stop', fixture => [
|
|
1006
|
+
provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
1007
|
+
hook_event_name: 'SubagentStart', agent_id: 'post-draft-reviewer', agent_type: 'reviewer' }, 'SubagentStart', fixture.root),
|
|
1008
|
+
]],
|
|
1009
|
+
['post-stop reviewer with duplicate Starts', fixture => {
|
|
1010
|
+
const start = provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
1011
|
+
hook_event_name: 'SubagentStart', agent_id: 'post-draft-reviewer', agent_type: 'reviewer' }, 'SubagentStart', fixture.root);
|
|
1012
|
+
return [start, { ...start, ref: `${start.ref}:dup`, captureId: `${start.captureId}:dup` }, provider.buildCaptureRecord({
|
|
1013
|
+
session_id: fixture.sessionId, cwd: fixture.root, hook_event_name: 'SubagentStop',
|
|
1014
|
+
agent_id: 'post-draft-reviewer', agent_type: 'reviewer' }, 'SubagentStop', fixture.root)];
|
|
1015
|
+
}],
|
|
1016
|
+
['post-stop reviewer Stop before Start', fixture => [
|
|
1017
|
+
provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
1018
|
+
hook_event_name: 'SubagentStop', agent_id: 'post-draft-reviewer', agent_type: 'reviewer' }, 'SubagentStop', fixture.root),
|
|
1019
|
+
provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
1020
|
+
hook_event_name: 'SubagentStart', agent_id: 'post-draft-reviewer', agent_type: 'reviewer' }, 'SubagentStart', fixture.root),
|
|
1021
|
+
]],
|
|
1022
|
+
]) {
|
|
1023
|
+
const fixture = createOutlineFixture();
|
|
1024
|
+
try {
|
|
1025
|
+
const completed = fixture.finishDraft({ postWorkerCaptures: arrange(fixture) });
|
|
1026
|
+
assert.ok(completed.result.diagnostics?.some(item => item.code === 'outline-competing-writer-start'),
|
|
1027
|
+
`${name} cannot use the post-stop exception: ${JSON.stringify(completed.result)}`);
|
|
1028
|
+
} finally { fixture.cleanup(); }
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const postDraftWriter = createOutlineFixture();
|
|
1032
|
+
try {
|
|
1033
|
+
const reviewer = 'post-draft-reviewer';
|
|
1034
|
+
const captures = [
|
|
1035
|
+
provider.buildCaptureRecord({ session_id: postDraftWriter.sessionId, cwd: postDraftWriter.root,
|
|
1036
|
+
hook_event_name: 'SubagentStart', agent_id: reviewer, agent_type: 'reviewer' }, 'SubagentStart', postDraftWriter.root),
|
|
1037
|
+
provider.buildCaptureRecord({ session_id: postDraftWriter.sessionId, cwd: postDraftWriter.root,
|
|
1038
|
+
hook_event_name: 'PostToolUse', tool_name: 'Edit', tool_use_id: 'post-draft-edit', agent_id: reviewer,
|
|
1039
|
+
tool_input: { file_path: postDraftWriter.target }, tool_response: { interrupted: false } }, 'PostToolUse', postDraftWriter.root),
|
|
1040
|
+
provider.buildCaptureRecord({ session_id: postDraftWriter.sessionId, cwd: postDraftWriter.root,
|
|
1041
|
+
hook_event_name: 'PostToolUse', tool_name: 'Task', tool_use_id: 'post-draft-review',
|
|
1042
|
+
tool_response: { agentId: reviewer, status: 'completed' } }, 'PostToolUse', postDraftWriter.root),
|
|
1043
|
+
provider.buildCaptureRecord({ session_id: postDraftWriter.sessionId, cwd: postDraftWriter.root,
|
|
1044
|
+
hook_event_name: 'SubagentStop', agent_id: reviewer, agent_type: 'reviewer' }, 'SubagentStop', postDraftWriter.root),
|
|
1045
|
+
];
|
|
1046
|
+
const completed = postDraftWriter.finishDraft({ postWorkerCaptures: captures });
|
|
1047
|
+
assert.ok(completed.result.diagnostics?.some(item => item.code === 'outline-writer-activity-ambiguous'),
|
|
1048
|
+
`a write after the post-stop reviewer start remains ambiguous: ${JSON.stringify(completed.result)}`);
|
|
1049
|
+
} finally { postDraftWriter.cleanup(); }
|
|
1050
|
+
|
|
1051
|
+
for (const [name, arrange] of [
|
|
1052
|
+
['reviewer stop after the dispatch', fixture => {
|
|
1053
|
+
const stop = fixture.reviewCaptures.findIndex(item => item.event === 'SubagentStop');
|
|
1054
|
+
fixture.extraCaptures.push(...fixture.reviewCaptures.splice(stop, 1));
|
|
1055
|
+
fixture.reviewCaptures.unshift(reviewerStart(fixture));
|
|
1056
|
+
}],
|
|
1057
|
+
['reviewer start without a stop', fixture => {
|
|
1058
|
+
fixture.reviewCaptures.unshift(provider.buildCaptureRecord({ session_id: fixture.sessionId, cwd: fixture.root,
|
|
1059
|
+
hook_event_name: 'SubagentStart', agent_id: 'other-agent', agent_type: 'reviewer' }, 'SubagentStart', fixture.root));
|
|
1060
|
+
}],
|
|
1061
|
+
['reviewer start after its stop', fixture => { fixture.reviewCaptures.push(reviewerStart(fixture)); }],
|
|
1062
|
+
['duplicate reviewer start with one stop before the dispatch', fixture => {
|
|
1063
|
+
fixture.reviewCaptures.unshift(reviewerStart(fixture), reviewerStart(fixture));
|
|
1064
|
+
}],
|
|
1065
|
+
['reviewer start with duplicate stops before the dispatch', fixture => {
|
|
1066
|
+
const stop = fixture.reviewCaptures.find(item => item.event === 'SubagentStop');
|
|
1067
|
+
fixture.reviewCaptures.unshift(reviewerStart(fixture));
|
|
1068
|
+
fixture.reviewCaptures.push({ ...stop, ref: `${stop.ref}:dup`, captureId: `${stop.captureId}:dup` });
|
|
1069
|
+
}],
|
|
1070
|
+
['reviewer start after the dispatch', fixture => { fixture.extraCaptures.push(reviewerStart(fixture)); }],
|
|
1071
|
+
]) {
|
|
1072
|
+
const fixture = createOutlineFixture();
|
|
1073
|
+
try {
|
|
1074
|
+
arrange(fixture);
|
|
1075
|
+
const completed = fixture.finishDraft();
|
|
1076
|
+
assert.ok(completed.result.diagnostics?.some(item => item.code === 'outline-competing-writer-start'),
|
|
1077
|
+
`${name} stays a competing writer start: ${JSON.stringify(completed.result)}`);
|
|
1078
|
+
} finally { fixture.cleanup(); }
|
|
1079
|
+
}
|
|
1080
|
+
|
|
914
1081
|
const unqualified = createOutlineFixture();
|
|
915
1082
|
try {
|
|
916
1083
|
const completed = unqualified.finishDraft();
|
|
@@ -1009,6 +1176,23 @@ const outlineMappingV2 = {
|
|
|
1009
1176
|
workerRootField: 'cwd',
|
|
1010
1177
|
};
|
|
1011
1178
|
|
|
1179
|
+
test('valid separate outline export opens outline qualification', () => {
|
|
1180
|
+
const staged = stageQualificationTree('source');
|
|
1181
|
+
try {
|
|
1182
|
+
const records = [qualificationDispatch(), qualificationStart(), qualificationResult(), qualificationStop()];
|
|
1183
|
+
const bytes = Buffer.from(records.map(record => JSON.stringify(record)).join('\n') + '\n');
|
|
1184
|
+
const file = 'doc-reconciliation-outline-capture.sanitized.jsonl';
|
|
1185
|
+
fs.writeFileSync(path.join(staged.libDir, file), bytes);
|
|
1186
|
+
staged.writeExport(staged.validExport({
|
|
1187
|
+
outlineDispatchCapture: { mapping: outlineMappingV2, file, sha256: sha(bytes), hostVersion: '2.1.312',
|
|
1188
|
+
run: 'run8', baseline: '1bd0f805031c5577a6e7f72a9bc5b41618b71fc1' },
|
|
1189
|
+
}));
|
|
1190
|
+
const outcome = require(path.join(staged.libDir, 'doc-reconciliation-provider.js'))
|
|
1191
|
+
._internals.validateOutlineCaptureQualification({ hostId: 'claude-code' });
|
|
1192
|
+
assert.strictEqual(outcome.open, true, JSON.stringify(outcome));
|
|
1193
|
+
} finally { staged.cleanup(); }
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1012
1196
|
function qualificationDispatch({ id = 'dispatch-1', promptId = 'shared-prompt', background = false, hasBackground = true,
|
|
1013
1197
|
isolation, hasIsolation = false, session = 'coordinator' } = {}) {
|
|
1014
1198
|
const toolInput = { prompt: dispatchLine, ...(hasBackground ? { run_in_background: background } : {}) };
|
|
@@ -1031,6 +1215,118 @@ function qualificationResult(id = 'dispatch-1', agentId = 'worker', status = 'co
|
|
|
1031
1215
|
...(legacyAgentId !== undefined ? { agent_id: legacyAgentId } : {}), tool_response: toolResponse };
|
|
1032
1216
|
}
|
|
1033
1217
|
|
|
1218
|
+
function stagedOutlineOutcome(staged) {
|
|
1219
|
+
return require(path.join(staged.libDir, 'doc-reconciliation-provider.js'))
|
|
1220
|
+
._internals.validateOutlineCaptureQualification({ hostId: 'claude-code' });
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function writeStagedOutlineEvidence(staged, records) {
|
|
1224
|
+
const file = 'doc-reconciliation-outline-capture.sanitized.jsonl';
|
|
1225
|
+
const bytes = Buffer.from(records.map(record => JSON.stringify(record)).join('\n') + '\n');
|
|
1226
|
+
fs.writeFileSync(path.join(staged.libDir, file), bytes);
|
|
1227
|
+
const wrapper = { mapping: outlineMappingV2, file, sha256: sha(bytes), hostVersion: '2.1.312', run: 'run8',
|
|
1228
|
+
baseline: '1bd0f805031c5577a6e7f72a9bc5b41618b71fc1' };
|
|
1229
|
+
staged.writeExport(staged.validExport({ outlineDispatchCapture: wrapper }));
|
|
1230
|
+
return { wrapper, filePath: path.join(staged.libDir, file), bytes };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
test('shipped run8 capture opens production qualification without DI', () => {
|
|
1234
|
+
const qualification = internals.validateOutlineCaptureQualification({ hostId: 'claude-code' });
|
|
1235
|
+
assert.strictEqual(qualification.open, true, JSON.stringify(qualification));
|
|
1236
|
+
assert.strictEqual(qualification.source, 'validated-export');
|
|
1237
|
+
assert.strictEqual(qualification.captureFile, 'doc-reconciliation-outline-capture.sanitized.jsonl');
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
test('shipped run8 export resolves the real-hook integrated fixture without fake qualification', () => {
|
|
1241
|
+
const originalFactory = provider.createClaudeOriginProvider;
|
|
1242
|
+
let originInput;
|
|
1243
|
+
provider.createClaudeOriginProvider = input => {
|
|
1244
|
+
originInput = input;
|
|
1245
|
+
return originalFactory(input);
|
|
1246
|
+
};
|
|
1247
|
+
const fixture = createOutlineFixture({ realHook: true, retainedInventory: true, noFakeQualification: true });
|
|
1248
|
+
try {
|
|
1249
|
+
const completed = fixture.finishDraft();
|
|
1250
|
+
assert.strictEqual(completed.result.status, 'accepted', JSON.stringify(completed.result));
|
|
1251
|
+
assert.strictEqual(Object.hasOwn(originInput, 'qualification'), false, 'production resolution must use the shipped export');
|
|
1252
|
+
} finally {
|
|
1253
|
+
provider.createClaudeOriginProvider = originalFactory;
|
|
1254
|
+
fixture.cleanup();
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
test('mapped row 17 dispatch accepts EVID-001 while the old VERI request and a substituted domain reject', () => {
|
|
1259
|
+
const valid = createOutlineFixture({ retainedInventory: true, noFakeQualification: true });
|
|
1260
|
+
try {
|
|
1261
|
+
const completed = valid.finishDraft();
|
|
1262
|
+
assert.strictEqual(completed.result.status, 'accepted', JSON.stringify(completed.result));
|
|
1263
|
+
assert.strictEqual(completed.result.event.outlineEvidence.inventoryRowId, '17');
|
|
1264
|
+
assert.strictEqual(completed.result.event.outlineEvidence.domain, 'EVID');
|
|
1265
|
+
} finally { valid.cleanup(); }
|
|
1266
|
+
|
|
1267
|
+
const unapprovedVeri = createOutlineFixture({ retainedInventory: true, rowId: '16',
|
|
1268
|
+
target: 'docs/adr/VERI-001.md', domain: 'VERI' });
|
|
1269
|
+
try {
|
|
1270
|
+
assert.strictEqual(unapprovedVeri.capture.outlineDispatch.state, 'unresolved');
|
|
1271
|
+
assert.ok(unapprovedVeri.capture.outlineDispatch.reasons.includes('outline-inventory-binding-mismatch'));
|
|
1272
|
+
} finally { unapprovedVeri.cleanup(); }
|
|
1273
|
+
|
|
1274
|
+
const unchangedRun8 = createOutlineFixture({ retainedInventory: true, rowId: '17',
|
|
1275
|
+
target: 'docs/adr/EVID-001-verify-all-evidence.md', domain: 'EVID' });
|
|
1276
|
+
try {
|
|
1277
|
+
assert.strictEqual(unchangedRun8.capture.outlineDispatch.state, 'unresolved');
|
|
1278
|
+
assert.ok(unchangedRun8.capture.outlineDispatch.reasons.includes('outline-domain-target-mismatch'),
|
|
1279
|
+
'the original descriptive filename still fails the citation-id/basename check');
|
|
1280
|
+
} finally { unchangedRun8.cleanup(); }
|
|
1281
|
+
|
|
1282
|
+
const substituted = createOutlineFixture({ retainedInventory: true,
|
|
1283
|
+
handoffOverrides: { inventory: { ...internals.OUTLINE_INVENTORY_PIN, rowId: '17', domain: 'VERI',
|
|
1284
|
+
targetPaths: ['docs/adr/EVID-001.md'] } } });
|
|
1285
|
+
try {
|
|
1286
|
+
assert.strictEqual(substituted.capture.outlineDispatch.state, 'unresolved');
|
|
1287
|
+
assert.ok(substituted.capture.outlineDispatch.reasons.includes('outline-inventory-binding-mismatch'));
|
|
1288
|
+
} finally { substituted.cleanup(); }
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
test('separate outline export failures stay closed for digest, path, file, wrapper, mapping, and chain corruption', () => {
|
|
1292
|
+
const staged = stageQualificationTree('source');
|
|
1293
|
+
const validRecords = [qualificationDispatch(), qualificationStart(), qualificationResult(), qualificationStop()];
|
|
1294
|
+
try {
|
|
1295
|
+
const closed = (name, mutate, records = validRecords) => {
|
|
1296
|
+
const { wrapper, filePath } = writeStagedOutlineEvidence(staged, records);
|
|
1297
|
+
mutate(wrapper, filePath);
|
|
1298
|
+
const outcome = stagedOutlineOutcome(staged);
|
|
1299
|
+
assert.strictEqual(outcome.open, false, `${name}: ${JSON.stringify(outcome)}`);
|
|
1300
|
+
};
|
|
1301
|
+
closed('wrong digest', wrapper => {
|
|
1302
|
+
wrapper.sha256 = '0'.repeat(64);
|
|
1303
|
+
staged.writeExport(staged.validExport({ outlineDispatchCapture: wrapper }));
|
|
1304
|
+
});
|
|
1305
|
+
closed('unsafe filename', wrapper => {
|
|
1306
|
+
wrapper.file = '../doc-reconciliation-outline-capture.sanitized.jsonl';
|
|
1307
|
+
staged.writeExport(staged.validExport({ outlineDispatchCapture: wrapper }));
|
|
1308
|
+
});
|
|
1309
|
+
closed('missing file', (_wrapper, filePath) => fs.rmSync(filePath));
|
|
1310
|
+
closed('malformed file', (wrapper, filePath) => {
|
|
1311
|
+
const bytes = Buffer.from('{malformed json}\n');
|
|
1312
|
+
fs.writeFileSync(filePath, bytes);
|
|
1313
|
+
wrapper.sha256 = sha(bytes);
|
|
1314
|
+
staged.writeExport(staged.validExport({ outlineDispatchCapture: wrapper }));
|
|
1315
|
+
});
|
|
1316
|
+
closed('malformed wrapper', () => staged.writeExport(staged.validExport({ outlineDispatchCapture: null })));
|
|
1317
|
+
closed('mapping mismatch', wrapper => {
|
|
1318
|
+
wrapper.mapping = { ...outlineMappingV2, workerRootField: 'cwd.unobserved' };
|
|
1319
|
+
staged.writeExport(staged.validExport({ outlineDispatchCapture: wrapper }));
|
|
1320
|
+
});
|
|
1321
|
+
const crossSession = validRecords.map(record => ({ ...record }));
|
|
1322
|
+
crossSession[2].session_id = 'other-coordinator';
|
|
1323
|
+
closed('only dispatch chain crosses coordinator sessions', () => {}, crossSession);
|
|
1324
|
+
const wrongRoot = validRecords.map(record => ({ ...record }));
|
|
1325
|
+
wrongRoot[1].cwd = '/tmp/other-worker';
|
|
1326
|
+
closed('only dispatch chain has a worker-root mismatch', () => {}, wrongRoot);
|
|
1327
|
+
} finally { staged.cleanup(); }
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1034
1330
|
function outlineCorrelation(records, anchor = records.find(item => item.event === 'PreToolUse')) {
|
|
1035
1331
|
return internals.correlateOutlineDispatch(records, anchor, '/tmp/prb-worker');
|
|
1036
1332
|
}
|
|
@@ -1175,22 +1471,13 @@ test('runtime resolution accepts Stop after the completed result through the syn
|
|
|
1175
1471
|
} finally { fixture.cleanup(); }
|
|
1176
1472
|
});
|
|
1177
1473
|
|
|
1178
|
-
test('
|
|
1179
|
-
const
|
|
1474
|
+
test('missing outline wrapper keeps outline-live-gate-unmet', () => {
|
|
1475
|
+
const staged = stageQualificationTree('source');
|
|
1180
1476
|
try {
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
principals: { author: fixture.sessionId, executors: [] },
|
|
1186
|
-
baseline: { root: fixture.root, commit: fixture.baselineCommit }, captures: completed.captures,
|
|
1187
|
-
sessionDir: fixture.sessionDir, taskBinding: fixture.taskBinding,
|
|
1188
|
-
outlineFixture: { expectedPin: fixture.expectedPin },
|
|
1189
|
-
});
|
|
1190
|
-
const result = production.resolveEvent({ reference: `${fixture.capture.ref}#outline-confirmation`, identity: completed.identity });
|
|
1191
|
-
assert.strictEqual(result.status, 'unavailable');
|
|
1192
|
-
assert.ok(result.diagnostics.some(item => item.code === 'outline-live-gate-unmet'), JSON.stringify(result));
|
|
1193
|
-
} finally { fixture.cleanup(); }
|
|
1477
|
+
staged.writeExport(staged.validExport());
|
|
1478
|
+
const qualification = stagedOutlineOutcome(staged);
|
|
1479
|
+
assert.strictEqual(qualification.open, false, JSON.stringify(qualification));
|
|
1480
|
+
} finally { staged.cleanup(); }
|
|
1194
1481
|
});
|
|
1195
1482
|
|
|
1196
1483
|
test('original B and immutable attempt chain survive reconfirmation; stale attempts/tokens never cover new scope', () => {
|
|
@@ -1181,12 +1181,14 @@ Only direct, single, registered Node invocations are credited. Compound shell co
|
|
|
1181
1181
|
|
|
1182
1182
|
Candidate classification follows command roles, not arbitrary text matches. A direct `doc-check`/`doc-check.js` target, a checker script operand to a recognized launcher, or a checker command in a recognized shell command-string remains a candidate when its launcher, subcommand, flags, quoting or composition is unsupported; it is refused as `unregistered-command` and cannot fall through to generic verification. An unrelated command whose data argument mentions `doc-check`—including workflow-plan JSON, `echo` text or a supplied filename—is not a candidate. Candidate detection never authorizes a wrapper, and opaque aliases or dynamically constructed targets remain unsupported.
|
|
1183
1183
|
|
|
1184
|
+
Default `check` remains non-generating, and `compare-derived` remains a pure comparison of caller-captured bytes. One explicitly requested, registered `check --derived-view <path>` invocation may generate the configured lifecycle index in memory and compare it with the declared view; it never writes the view. Automatic collection and reconciliation still do not execute generators.
|
|
1185
|
+
|
|
1184
1186
|
| Capability | Credit needs |
|
|
1185
1187
|
|---|---|
|
|
1186
1188
|
| `doc-lint` | One projection from an observed successful `check`: lint exit 0, valid schema, the exact expected governed and baseline paths, and complete relevant coverage |
|
|
1187
1189
|
| `link-check` | A separate projection of the same invocation, scoped to the expected paths: no broken, skipped, unreadable or unsupported link syntax |
|
|
1188
|
-
| `derived-regenerate` | An observed successful generation
|
|
1189
|
-
| `quality-review` | A completed review from a correlated host `agent_id` distinct from the author session and every executor. Executors are the author session and the principals of registered doc-check runs in the task, which are the execution evidence a doc obligation uses (see the rationale below). An Agent/Task result must name the same `agent_id`. The
|
|
1190
|
+
| `derived-regenerate` | An observed successful generation and an observed successful pure byte comparison, bound to the same source inventory, config identity and generated bytes. Both operations MAY occur inside one explicitly requested, registered checker invocation: the checker executes the supported configured generator, retains its success and generated-output digest, and compares that output with the declared view without writing the view. Credit requires unchanged input identities and an observed matching target digest. Caller-reported `--generation-status ok` cannot establish generation alone |
|
|
1191
|
+
| `quality-review` | A completed review from a correlated host `agent_id` distinct from the author session and every executor. Executors are the author session and the principals of registered doc-check runs in the task, which are the execution evidence a doc obligation uses (see the rationale below). An Agent/Task result must name the same `agent_id`. The pilot offers only SubagentStop records with an explicit structured `Review verdict: approve|reject`; ordinary completion summaries are not review candidates, while missing or malformed required review remains unresolved. An approve needs a non-empty summary and `Reviewed artifact: <path> sha256:<hex>` lines whose exact path set matches the obligation scope and candidate. SubagentStop alone is never approval |
|
|
1190
1192
|
| `conflict-check` | A registered `check --conflict-assessment` runs lifecycle lint with `--all` and requires an exact `--base` commit SHA; the corpus collection must be complete and lint must have no error diagnostics. The event keeps the exact obligation `normativePaths` as its scope and binds the full corpus paths/digests, config bytes and base commit separately to the workflow snapshot. This checks recorded and structural conflicts only; prose contradictions stay with independent `quality-review`. Plain `check` earns no conflict credit |
|
|
1191
1193
|
| `requirement-sync` | Stay unresolved. F4 gives it no credit |
|
|
1192
1194
|
| SHOULD reminders | Never earn credit, and their absence never withholds it |
|
|
@@ -1213,7 +1215,7 @@ claude -p "<fixed capture prompt, containing the exact check command>" \
|
|
|
1213
1215
|
|
|
1214
1216
|
**Retained artifacts** go to `benchmarks/results/live-host/claude-325-3b-s2b/`: `capture.sanitized.jsonl`, `README.md`, the relevant stream-json transcript, settings, manifest and wrapper digests, the host version, the runtime and tree fingerprint, and the command and exit summary. Raw payloads stay private, outside the repository. Transcript text alone is not hook evidence.
|
|
1215
1217
|
|
|
1216
|
-
**Qualification export** is
|
|
1218
|
+
**Qualification export** is the sanitized file at `hooks/scripts/lib/doc-reconciliation-qualification.json`, copied by plugin sync. Its base `sanitizedCapture` and validation stay unchanged. Outline qualification uses a separate `outlineDispatchCapture` wrapper and sibling file (`doc-reconciliation-outline-capture.sanitized.jsonl`); the runtime checks the wrapper's sibling filename, SHA-256, object records and exact outline mapping. Neither path searches ancestor directories or accepts a file by size.
|
|
1217
1219
|
|
|
1218
1220
|
### Scope and limits
|
|
1219
1221
|
|
|
@@ -1232,7 +1234,7 @@ claude -p "<fixed capture prompt, containing the exact check command>" \
|
|
|
1232
1234
|
|
|
1233
1235
|
## PR-B: dedicated pre-draft outline confirmation
|
|
1234
1236
|
|
|
1235
|
-
Status: **implemented behind the existing pilot switch; mechanism and package checks pass;
|
|
1237
|
+
Status: **implemented behind the existing pilot switch; mechanism and package checks pass; the retained Claude Code 2.1.312 run8 profile is qualified for outline evidence.** Run8 was captured at main baseline `1bd0f805031c5577a6e7f72a9bc5b41618b71fc1`. This does not establish runtime version enforcement or an E1 qualifying cycle. OpenCode, Codex and other Claude Code versions remain unqualified. The capability follows owner decisions 9 and 11: an independent reviewer approves exact O before drafting; the outline cites one row from the pinned inventory; final D is a strict descendant of O; a coordinator handoff records review-complete → fresh-draft-dispatch. Owner approval is provenance only and never a runtime reviewer principal.
|
|
1236
1238
|
|
|
1237
1239
|
```mermaid
|
|
1238
1240
|
flowchart LR
|
|
@@ -1250,12 +1252,14 @@ flowchart LR
|
|
|
1250
1252
|
| Evidence | Required binding |
|
|
1251
1253
|
|---|---|
|
|
1252
1254
|
| 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. |
|
|
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. |
|
|
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. |
|
|
1255
|
+
| 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 trusted pin also records the owner-approved `VERI`→`EVID` mapping for rows 15–18 by reference to `../e1-feasibility-2026-10-28/owner-decision-veri-evid.md`; inventory bytes and digest stay unchanged. 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. |
|
|
1256
|
+
| 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, with `id` equal to the target basename, 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. |
|
|
1255
1257
|
| 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
1258
|
| 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. |
|
|
1257
1259
|
| 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. |
|
|
1258
1260
|
|
|
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.
|
|
1261
|
+
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. A same-root subagent start is unresolved as a competing writer when it may overlap the exclusive draft interval, from accepted dispatch PreToolUse through the uniquely correlated draft worker’s Stop. A uniquely identified agent whose Start and unique Stop both precede dispatch, or whose Start occurs strictly after that worker Stop, does not establish overlap by its Start alone. Missing, duplicated or inconsistent lifecycle evidence stays unresolved. This exception does not authorize writes outside the correlated draft worker/root or relax independent review, artifact identity or final-byte validation.
|
|
1262
|
+
|
|
1263
|
+
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.
|
|
1260
1264
|
|
|
1261
|
-
The
|
|
1265
|
+
The separate run8 capture establishes the versioned `claude-outline-dispatch-mapping/2` field contract for the observed Claude Code 2.1.312 profile: prompt and coordinator identity, completed result status and agent ID, explicit non-background dispatch, isolation absence, and matching worker root. The production export reads this capture from its own sibling file and keeps the base qualification capture unchanged. The retained run8 assessment reports `resolveIfQualified.status: accepted`; it is not an independent rerun. Deterministic fixtures continue to exercise the real hook/provider/adapter/checker/pilot path with a synthetic inventory pin as mechanism evidence. Neither evidence source is an E1 qualifying cycle or a claim for OpenCode, Codex or other Claude Code versions.
|
|
@@ -210,7 +210,7 @@ The editorial doc check (`harness-everything/scripts/doc-check.js`, F1) is reach
|
|
|
210
210
|
|
|
211
211
|
Credited reconciliation for F4 (Claude Code only, pilot ON) is **implemented, with mechanism and package evidence and one live field-availability capture**. Credit works for `doc-lint` and `link-check` through a registered `doc-check` run, and for an independent `quality-review` with bound path digests. The capture under `benchmarks/results/live-host/claude-325-3b-s2b/` proves field availability on Claude Code 2.1.308 for one session. It is not live-host credit, and no live-host credit run exists yet. Deterministic mechanism tests (replay through the real hook entry points, with a CI-only qualification preloader) prove the chain.
|
|
212
212
|
|
|
213
|
-
PR-B adds a dedicated `outline-confirmation` evidence lane for Claude captures with separate B/O/D checks, a coordinator-sealed pre-dispatch handoff, pinned-inventory row binding, and an independent exact-O review.
|
|
213
|
+
PR-B adds a dedicated `outline-confirmation` evidence lane for Claude captures with separate B/O/D checks, a coordinator-sealed pre-dispatch handoff, pinned-inventory row binding, and an independent exact-O review. A separate sanitized capture now qualifies the **observed Claude Code 2.1.312 run8 profile**, recorded at main baseline `1bd0f805031c5577a6e7f72a9bc5b41618b71fc1`. This is retained field-mapping evidence, not runtime version enforcement or an E1 qualifying cycle. OpenCode, Codex and other Claude Code versions receive no outline qualification. Missing or ambiguous capture stays unresolved and host tools remain fail-open. The outline result does not replace the aggregate B→D MUST result or the existing exact-D `quality-review` contract.
|
|
214
214
|
|
|
215
215
|
### Own-repository governance guard (PR #376)
|
|
216
216
|
|