harness-everything 0.46.3 → 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.
Files changed (32) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +23 -1
  4. package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/README.md +33 -8
  5. package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md +128 -0
  6. package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/sanitize-outline-capture.js +105 -0
  7. package/ci/mechanism-e1-derived-regenerate.test.js +333 -0
  8. package/ci/mechanism-outline-sanitizer.test.js +79 -0
  9. package/ci/mechanism-prb-outline-review.test.js +278 -31
  10. package/docs/doc-workflow-reconciliation.md +12 -8
  11. package/docs/platform-capabilities.md +1 -1
  12. package/harness-everything/SKILL.md +1 -1
  13. package/harness-everything/scripts/doc-check.js +106 -8
  14. package/harness-everything/scripts/doc-lifecycle.js +49 -1
  15. package/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
  16. package/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
  17. package/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
  18. package/hooks/scripts/lib/doc-reconciliation-provider.js +168 -15
  19. package/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
  20. package/opencode-plugin/plugin.json +1 -1
  21. package/package.json +1 -1
  22. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  23. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +22 -0
  24. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-outline-capture.sanitized.jsonl +24 -0
  25. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -1
  26. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +168 -15
  27. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +20 -0
  28. package/plugins/harness-everything/plugin.json +1 -1
  29. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  30. package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +106 -8
  31. package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +49 -1
  32. package/sbom.cdx.json +7 -7
@@ -47,7 +47,7 @@ const QUALIFICATION_EXPORT_SCHEMA = 'harness.doc-reconciliation.qualification.v1
47
47
  const HEX64 = /^[0-9a-f]{64}$/;
48
48
  const PRE_CAPTURE_TOOLS = ['Bash', 'PowerShell', 'Task', 'Agent', 'Write', 'Edit', 'apply_patch'];
49
49
  const STDOUT_CAP_BYTES = 1024 * 1024;
50
- const DOC_CHECK_FLAGS = new Set(['--workspace', '--config', '--changed-path', '--base']);
50
+ const DOC_CHECK_FLAGS = new Set(['--workspace', '--config', '--changed-path', '--base', '--derived-view']);
51
51
  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';
@@ -76,6 +76,8 @@ const OUTLINE_INVENTORY_PIN = Object.freeze({
76
76
  runId: 'e1-inv-1',
77
77
  inputSha: '0ef0541c6cae550e8269ba712e03797e0e987dfb',
78
78
  approvalRef: { issue: 325, commentId: '6436207729', decision: 1 },
79
+ rowDomainMappings: Object.freeze([Object.freeze({ rowIds: Object.freeze(['15', '16', '17', '18']),
80
+ sourceDomain: 'VERI', domain: 'EVID', approvalRef: '../e1-feasibility-2026-10-28/owner-decision-veri-evid.md' })]),
79
81
  retainedArtifactPath: `inventory/${OUTLINE_INVENTORY_SHA256}.md`,
80
82
  });
81
83
  const OUTLINE_MARKER = 'HARNESS_OUTLINE_DISPATCH_V1';
@@ -99,7 +101,7 @@ function isUncPathPrefix(token, command, index) {
99
101
  return token === '' && command[index] === '\\' && command[index + 1] === '\\';
100
102
  }
101
103
  // Canonical SHA-256 of harness-everything/scripts/doc-check.js (CRLF normalised to LF). Re-pin deliberately when that script changes.
102
- const SHIPPED_DOC_CHECK_DIGEST = '1afd94d66b356c94206d2441529ec0b7fafa1bcb453b0f1e2cf6994b1e4858e5';
104
+ const SHIPPED_DOC_CHECK_DIGEST = '804a093b31afde07ea6ec40e39fc758b501966bfa2a15c53a5275ad3171b0755';
103
105
  // This install's own doc-check runtime, relative to hooks/scripts/lib: the source checkout or the packaged plugin.
104
106
  const DOC_CHECK_INSTALL_LAYOUTS = [
105
107
  ['..', '..', '..', 'harness-everything', 'scripts', 'doc-check.js'],
@@ -405,6 +407,8 @@ function resolveDocCheckInvocation(command, cwd) {
405
407
  if (flag === '--changed-path') changedPaths.push(value);
406
408
  else {
407
409
  if (values.has(flag)) return rejected('duplicate-flag');
410
+ if (flag === '--derived-view' && (path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value)
411
+ || value.split(/[\\/]/).includes('..') || /[*?\[\]{}]/.test(value))) return rejected('derived-view-invalid');
408
412
  values.set(flag, value);
409
413
  }
410
414
  }
@@ -425,7 +429,8 @@ function resolveDocCheckInvocation(command, cwd) {
425
429
  }
426
430
  }
427
431
  return { kind: 'accepted', reason: null, installed: registered.installed, digest, argv,
428
- conflictAssessment, base: values.get('--base') || null, changedPaths };
432
+ conflictAssessment, base: values.get('--base') || null, config: values.get('--config'),
433
+ derivedView: values.get('--derived-view') || null, changedPaths };
429
434
  }
430
435
 
431
436
  // Observation-time digests of the checked paths, read from the workspace when the check ran. Absent paths are omitted.
@@ -439,6 +444,29 @@ function observedDigestsFor(root, paths) {
439
444
  return digests;
440
445
  }
441
446
 
447
+ function observedConfigSourcesFor(root, sources) {
448
+ if (!Array.isArray(sources)) return [];
449
+ return sources.map(source => {
450
+ const relative = source && source.path;
451
+ if (typeof relative !== 'string' || !relative || path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..')) {
452
+ return { path: String(relative || ''), presence: 'unsupported' };
453
+ }
454
+ const file = path.resolve(root, relative);
455
+ const within = path.relative(path.resolve(root), file);
456
+ if (!within || within === '..' || within.startsWith(`..${path.sep}`) || path.isAbsolute(within)) {
457
+ return { path: relative, presence: 'unsupported' };
458
+ }
459
+ try {
460
+ if (!fs.lstatSync(file).isFile()) return { path: relative, presence: 'unsupported' };
461
+ return { path: relative, presence: 'present', digest: crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') };
462
+ } catch (error) {
463
+ return error.code === 'ENOENT' || error.code === 'ENOTDIR'
464
+ ? { path: relative, presence: 'missing' }
465
+ : { path: relative, presence: 'unreadable' };
466
+ }
467
+ });
468
+ }
469
+
442
470
  function textOf(value) { return typeof value === 'string' && value.trim().length > 0; }
443
471
  function recordOf(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
444
472
  function compareKeys(a, b) { return a < b ? -1 : a > b ? 1 : 0; }
@@ -695,7 +723,7 @@ function parseInventoryPinRows(bytes) {
695
723
 
696
724
  function validateOutlineInventoryPin({ bytes, pin, expectedPin = OUTLINE_INVENTORY_PIN } = {}) {
697
725
  if (!Buffer.isBuffer(bytes) || !recordOf(pin) || !recordOf(expectedPin)) return { state: 'unresolved', reason: 'inventory-unavailable' };
698
- const fields = ['source', 'sha256', 'runId', 'inputSha', 'approvalRef', 'retainedArtifactPath'];
726
+ const fields = ['source', 'sha256', 'runId', 'inputSha', 'approvalRef', 'rowDomainMappings', 'retainedArtifactPath'];
699
727
  const actualPin = Object.fromEntries(fields.map(field => [field, pin[field]]));
700
728
  const trustedPin = Object.fromEntries(fields.map(field => [field, expectedPin[field]]));
701
729
  if (captureJson(actualPin) !== captureJson(trustedPin)) return { state: 'unresolved', reason: 'inventory-pin-mismatch' };
@@ -716,10 +744,18 @@ function resolveInventoryRow(inventory, rowId) {
716
744
  if (!parsed || parsed.state !== 'parsed' || !textOf(rowId)) return { state: 'unresolved', reason: 'inventory-row-missing' };
717
745
  const matches = [...parsed.rows.entries()].filter(([id]) => id === rowId);
718
746
  const domains = parsed.domains.get(rowId) || [];
719
- if (matches.length !== 1 || matches[0][1] !== 1 || domains.length !== 1) {
747
+ if (matches.length !== 1 || matches[0][1] !== 1) {
720
748
  return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
721
749
  }
722
- return { state: 'accepted', row: { rowId, domain: domains[0] } };
750
+ const mappings = (Array.isArray(inventory.pin?.rowDomainMappings) ? inventory.pin.rowDomainMappings : [])
751
+ .filter(item => recordOf(item) && Array.isArray(item.rowIds) && item.rowIds.includes(rowId));
752
+ if (mappings.length > 1) return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
753
+ const mapping = mappings[0];
754
+ if (mapping && (!domains.includes(mapping.sourceDomain) || !textOf(mapping.domain) || !textOf(mapping.approvalRef))) {
755
+ return { state: 'unresolved', reason: 'inventory-row-mapping-invalid' };
756
+ }
757
+ if (!mapping && domains.length !== 1) return { state: 'unresolved', reason: 'inventory-row-ambiguous' };
758
+ return { state: 'accepted', row: { rowId, domain: mapping ? mapping.domain : domains[0] } };
723
759
  }
724
760
 
725
761
  function hasSubstantiveDecision(text) {
@@ -1165,11 +1201,20 @@ function validateOutlineCaptureQualification(input, expectedHost = HOST_ID) {
1165
1201
  const qualificationError = validateQualificationExport(expectedHost);
1166
1202
  if (qualificationError) return { open: false, reason: qualificationError };
1167
1203
  const exported = JSON.parse(fs.readFileSync(path.join(__dirname, EXPORT_FILE), 'utf8'));
1168
- const mapping = exported.outlineDispatchCapture;
1169
- const source = exported.sanitizedCapture;
1170
- const records = fs.readFileSync(path.join(__dirname, source.file), 'utf8').split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
1171
- if (!outlineCaptureMappingValid(mapping, records)) return { open: false, reason: 'outline-live-capture-unqualified' };
1172
- return { open: true, source: 'validated-export', captureFile: source.file };
1204
+ const wrapper = exported.outlineDispatchCapture;
1205
+ if (!recordOf(wrapper) || !textOf(wrapper.hostVersion) || !textOf(wrapper.run) || !textOf(wrapper.baseline)
1206
+ || !textOf(wrapper.file) || /[\\/]/.test(wrapper.file) || wrapper.file.includes('..')) {
1207
+ return { open: false, reason: 'outline-live-capture-unqualified' };
1208
+ }
1209
+ const bytes = fs.readFileSync(path.join(__dirname, wrapper.file));
1210
+ if (crypto.createHash('sha256').update(bytes).digest('hex') !== wrapper.sha256) {
1211
+ return { open: false, reason: 'outline-live-capture-unqualified' };
1212
+ }
1213
+ const records = bytes.toString('utf8').split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line));
1214
+ if (!records.every(recordOf) || !outlineCaptureMappingValid(wrapper.mapping, records)) {
1215
+ return { open: false, reason: 'outline-live-capture-unqualified' };
1216
+ }
1217
+ return { open: true, source: 'validated-export', captureFile: wrapper.file };
1173
1218
  } catch (error) {
1174
1219
  return { open: false, reason: `outline-live-capture-unqualified:${error.code || 'invalid'}` };
1175
1220
  }
@@ -1456,20 +1501,31 @@ function resolveOutlineEvidence({ safeInput, capture, captures, request, originR
1456
1501
  // are covered by the clean-O seam check and by unattributedWriters below.
1457
1502
  const journal = Array.isArray(captures) ? captures : [];
1458
1503
  const dispatchIndex = journal.indexOf(correlation.pre);
1459
- const stoppedBeforeDispatch = start => {
1504
+ const uniqueLifecycle = start => {
1505
+ if (!textOf(start.session_id) || !textOf(start.agent_id)) return null;
1460
1506
  const lifecycle = event => journal.filter(item => recordOf(item) && item.event === event
1461
1507
  && item.session_id === start.session_id && textOf(start.agent_id) && item.agent_id === start.agent_id);
1462
1508
  // Exactly one Start and one Stop per (session_id, agent_id); a duplicate either way stays ambiguous.
1463
1509
  const starts = lifecycle('SubagentStart');
1464
1510
  const stops = lifecycle('SubagentStop');
1465
- if (starts.length !== 1 || starts[0] !== start || stops.length !== 1) return false;
1511
+ if (starts.length !== 1 || starts[0] !== start || stops.length !== 1) return null;
1466
1512
  const startIndex = journal.indexOf(start);
1467
1513
  const stopIndex = journal.indexOf(stops[0]);
1468
- return dispatchIndex >= 0 && startIndex >= 0 && startIndex < stopIndex && stopIndex < dispatchIndex;
1514
+ if (startIndex < 0 || stopIndex <= startIndex) return null;
1515
+ return { startIndex, stopIndex };
1516
+ };
1517
+ const stoppedBeforeDispatch = start => {
1518
+ const lifecycle = uniqueLifecycle(start);
1519
+ return Boolean(lifecycle && dispatchIndex >= 0 && lifecycle.stopIndex < dispatchIndex);
1520
+ };
1521
+ const startedAfterWorkerStop = start => {
1522
+ const lifecycle = uniqueLifecycle(start);
1523
+ const workerStopIndex = journal.indexOf(correlation.stop);
1524
+ return Boolean(lifecycle && workerStopIndex >= 0 && lifecycle.startIndex > workerStopIndex);
1469
1525
  };
1470
1526
  const competingStarts = journal.filter(item => recordOf(item)
1471
1527
  && item.event === 'SubagentStart' && item.ref !== worker.ref && sameCanonicalPath(item.cwd, workerRoot)
1472
- && !stoppedBeforeDispatch(item));
1528
+ && !stoppedBeforeDispatch(item) && !startedAfterWorkerStop(item));
1473
1529
  if (competingStarts.length) return fail('outline-competing-writer-start');
1474
1530
  const unattributedWriters = (Array.isArray(captures) ? captures : []).filter(item => possibleWriterCapture(item)
1475
1531
  && (item.agent_id !== worker.agent_id || !sameCanonicalPath(item.cwd, workerRoot)));
@@ -1725,6 +1781,92 @@ const createClaudeOriginProvider = input => {
1725
1781
  if (!paths.length) return rejected('scope-empty', `${capability} checked no governed paths`);
1726
1782
  let corpusBinding;
1727
1783
  let baselineArtifactDigests;
1784
+ let derivedBinding;
1785
+ if (capability === 'derived-regenerate') {
1786
+ if (!registered.derivedView || paths.length !== 1 || paths[0] !== registered.derivedView) {
1787
+ return rejected('capability-mismatch', 'derived-regenerate requires the exact registered --derived-view path');
1788
+ }
1789
+ const binding = projection.binding;
1790
+ const corpus = binding && binding.sourceCorpus;
1791
+ const config = binding && binding.config;
1792
+ const generator = binding && binding.generator;
1793
+ const generation = binding && binding.generation;
1794
+ const comparison = binding && binding.comparison;
1795
+ if (!recordOf(binding) || binding.view !== registered.derivedView || !recordOf(corpus) || corpus.complete !== true
1796
+ || !Array.isArray(corpus.paths) || !corpus.paths.length || !recordOf(corpus.digests)
1797
+ || JSON.stringify([...new Set(corpus.paths)].sort(compareKeys)) !== JSON.stringify(corpus.paths)
1798
+ || JSON.stringify(Object.keys(corpus.digests).sort(compareKeys)) !== JSON.stringify(corpus.paths)
1799
+ || corpus.paths.some(relative => !textOf(relative) || path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..')
1800
+ || !HEX64.test(String(corpus.digests[relative] || '')))
1801
+ || !recordOf(config) || !textOf(config.path) || path.isAbsolute(config.path)
1802
+ || config.path.split(/[\\/]/).includes('..') || !HEX64.test(String(config.digest || ''))
1803
+ || !Array.isArray(config.sources) || !recordOf(generator) || !textOf(generator.path)
1804
+ || !HEX64.test(String(generator.digest || '')) || !recordOf(generation) || generation.state !== 'pass'
1805
+ || !HEX64.test(String(generation.outputDigest || '')) || !recordOf(comparison) || comparison.state !== 'equal'
1806
+ || comparison.targetDigest !== generation.outputDigest) {
1807
+ return rejected('derived-binding-invalid', 'derived-regenerate stdout lacks complete generation, comparison, and input binding');
1808
+ }
1809
+ const configPath = path.resolve(cap.cwd, registered.config);
1810
+ const configRelative = path.relative(path.resolve(cap.cwd), configPath).split(path.sep).join('/');
1811
+ const observedDerived = recordOf(cap.observedDerivedDigests) ? cap.observedDerivedDigests : {};
1812
+ if (configRelative !== config.path || observedDerived[config.path] !== config.digest
1813
+ || observedDerived[registered.derivedView] !== comparison.targetDigest) {
1814
+ return rejected('derived-input-changed-after-check', 'the selected config or derived target changed before host capture');
1815
+ }
1816
+ const expectedObservedPaths = [...new Set([...corpus.paths, config.path, registered.derivedView])].sort(compareKeys);
1817
+ if (JSON.stringify(Object.keys(observedDerived).sort(compareKeys)) !== JSON.stringify(expectedObservedPaths)
1818
+ || corpus.paths.some(relative => observedDerived[relative] !== corpus.digests[relative])) {
1819
+ return rejected('derived-input-changed-after-check', 'a source path is missing or changed before host capture');
1820
+ }
1821
+ const configSources = config.sources;
1822
+ if (JSON.stringify(configSources.map(item => item && item.path)) !== JSON.stringify([...new Set(configSources.map(item => item && item.path))].sort(compareKeys))
1823
+ || configSources.some(item => !recordOf(item) || !textOf(item.path) || path.isAbsolute(item.path)
1824
+ || item.path.split(/[\\/]/).includes('..') || !['present', 'missing'].includes(item.presence)
1825
+ || (item.presence === 'present' ? !HEX64.test(String(item.digest || '')) : item.digest !== undefined))) {
1826
+ return rejected('derived-config-binding-invalid', 'derived-regenerate config source inventory is malformed');
1827
+ }
1828
+ const observedConfigSources = cap.observedDerivedConfigSources;
1829
+ if (!Array.isArray(observedConfigSources) || JSON.stringify(observedConfigSources) !== JSON.stringify(configSources)) {
1830
+ return rejected('config-changed-after-check', 'a config source changed between check and host capture');
1831
+ }
1832
+ const configSource = configSources.find(item => item.path === config.path);
1833
+ if (!configSource || configSource.presence !== 'present' || configSource.digest !== config.digest) {
1834
+ return rejected('derived-config-binding-invalid', 'the selected config is absent from the observed config inventory');
1835
+ }
1836
+ let configData;
1837
+ try { configData = JSON.parse(fs.readFileSync(configPath, 'utf8')); }
1838
+ catch { return rejected('derived-config-binding-invalid', 'the selected config cannot be read during assessment'); }
1839
+ const selectedViews = Array.isArray(configData.derived) ? configData.derived.filter(item => item && item.path === registered.derivedView) : [];
1840
+ const expectedGenerator = `node harness-everything/scripts/doc-lifecycle.js index --workspace . --config ${registered.config} --all`;
1841
+ if (selectedViews.length !== 1 || selectedViews[0].generator !== expectedGenerator) {
1842
+ return rejected('derived-generator-unsupported', 'the selected config does not declare the supported lifecycle index operation');
1843
+ }
1844
+ let generatorPath;
1845
+ try { generatorPath = fs.realpathSync(path.join(path.dirname(registered.installed), 'doc-lifecycle.js')); }
1846
+ catch { return rejected('derived-generator-unavailable', 'the installed lifecycle index generator is missing'); }
1847
+ if (generator.path !== generatorPath || fileDigest(generatorPath) !== generator.digest) {
1848
+ return rejected('derived-generator-mismatch', 'the lifecycle generator differs from the implementation observed in stdout');
1849
+ }
1850
+ let currentConfigSources;
1851
+ try {
1852
+ const lifecycle = require(generatorPath);
1853
+ currentConfigSources = lifecycle.configSourceInventory(cap.cwd, configPath);
1854
+ } catch { return rejected('derived-config-binding-invalid', 'the lifecycle config inventory is unavailable'); }
1855
+ if (!currentConfigSources || currentConfigSources.complete !== true
1856
+ || JSON.stringify(currentConfigSources.sources) !== JSON.stringify(configSources)) {
1857
+ return rejected('config-changed-after-check', 'the lifecycle config source inventory changed after the check');
1858
+ }
1859
+ if (!recordOf(request.identity) || !HEX64.test(String(request.identity.baselineDigest || ''))
1860
+ || !HEX64.test(String(request.identity.candidateDigest || '')) || !HEX64.test(String(request.identity.configDigest || ''))) {
1861
+ return rejected('identity-missing', 'derived-regenerate needs the complete snapshot identity');
1862
+ }
1863
+ derivedBinding = {
1864
+ ...binding,
1865
+ baselineDigest: request.identity.baselineDigest,
1866
+ candidateDigest: request.identity.candidateDigest,
1867
+ configIdentityDigest: request.identity.configDigest,
1868
+ };
1869
+ }
1728
1870
  if (capability === 'conflict-check') {
1729
1871
  const assessment = report.conflictAssessment;
1730
1872
  const corpus = assessment && assessment.corpus;
@@ -1797,6 +1939,7 @@ const createClaudeOriginProvider = input => {
1797
1939
  artifactDigests,
1798
1940
  ...(Object.keys(baselineArtifacts).length ? { baselineArtifacts } : {}),
1799
1941
  ...(corpusBinding ? { corpusBinding } : {}),
1942
+ ...(derivedBinding ? { derivedBinding } : {}),
1800
1943
  captured: { responseRef: cap.responseRef, responseDigest: cap.responseDigest },
1801
1944
  };
1802
1945
  return { status: 'accepted', originRef, event, correlation: { toolCorrelationId: cap.tool_use_id, resultRef: cap.responseRef } };
@@ -2131,6 +2274,16 @@ function buildCaptureRecord(payload, eventName, workspaceRoot) {
2131
2274
  const report = JSON.parse(stdout);
2132
2275
  checkedPaths = ['doc-lint', 'link-check'].flatMap(capability => (recordOf(report.capabilities) && recordOf(report.capabilities[capability])
2133
2276
  && Array.isArray(report.capabilities[capability].checkedPaths) ? report.capabilities[capability].checkedPaths : []));
2277
+ const derived = recordOf(report.capabilities) && report.capabilities['derived-regenerate'];
2278
+ const derivedBinding = recordOf(derived) && recordOf(derived.binding) ? derived.binding : null;
2279
+ if (installedCheck.derivedView && typeof workspaceRoot === 'string' && workspaceRoot && derivedBinding && derivedBinding.view === installedCheck.derivedView
2280
+ && recordOf(derivedBinding.sourceCorpus) && Array.isArray(derivedBinding.sourceCorpus.paths)
2281
+ && recordOf(derivedBinding.config) && Array.isArray(derivedBinding.config.sources)) {
2282
+ checkedPaths.push(derivedBinding.view);
2283
+ record.observedDerivedDigests = observedDigestsFor(workspaceRoot,
2284
+ [...derivedBinding.sourceCorpus.paths, derivedBinding.config.path, derivedBinding.view]);
2285
+ record.observedDerivedConfigSources = observedConfigSourcesFor(workspaceRoot, derivedBinding.config.sources);
2286
+ }
2134
2287
  const conflict = recordOf(report.capabilities) && report.capabilities['conflict-check'];
2135
2288
  if (installedCheck.conflictAssessment === true && report.mode === 'conflict-assessment'
2136
2289
  && recordOf(conflict) && conflict.state === 'pass' && recordOf(conflict.corpus)) {
@@ -30,5 +30,25 @@
30
30
  "sanitizedCapture": {
31
31
  "file": "doc-reconciliation-capture.sanitized.jsonl",
32
32
  "sha256": "db4d8c85d2ba0fd06f53f455f31b125dd194e9a22cb337687bc0e295a16f02e5"
33
+ },
34
+ "outlineDispatchCapture": {
35
+ "mapping": {
36
+ "version": "claude-outline-dispatch-mapping/2",
37
+ "promptField": "tool_input.prompt",
38
+ "dispatchKey": "session_id+tool_use_id",
39
+ "runInBackgroundField": "tool_input.run_in_background",
40
+ "isolationPresenceField": "tool_input.isolation",
41
+ "resultAgentIdField": "tool_response.agentId",
42
+ "resultStatusField": "tool_response.status",
43
+ "workerStartEvent": "SubagentStart",
44
+ "workerStopEvent": "SubagentStop",
45
+ "workerIdentity": "session_id+agent_id",
46
+ "workerRootField": "cwd"
47
+ },
48
+ "file": "doc-reconciliation-outline-capture.sanitized.jsonl",
49
+ "sha256": "6feb440af12aaf878a0bed5cb750f29e27d271a9add93001e151746980230b7f",
50
+ "hostVersion": "2.1.312",
51
+ "run": "run8",
52
+ "baseline": "1bd0f805031c5577a6e7f72a9bc5b41618b71fc1"
33
53
  }
34
54
  }
@@ -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.3",
4
+ "version": "0.47.0",
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.3",
3
+ "version": "0.47.0",
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.3",
3
+ "version": "0.47.0",
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",
@@ -226,6 +226,28 @@ function acceptDocEvents(input = {}, dependencies = {}) {
226
226
  if (!configSource || configSource.presence !== 'present'
227
227
  || U.sha(U.bytes(configSource.bytes)) !== binding?.config?.digest) reject('config-capture-mismatch');
228
228
  }
229
+ if (event?.capability === 'derived-regenerate') {
230
+ const binding = event.derivedBinding;
231
+ const corpus = binding?.sourceCorpus;
232
+ const expectedPaths = U.runtimeModule('doc-lifecycle.js').documentCorpusPaths(
233
+ snapshot.candidate.records, snapshot.config.resolution.config
234
+ );
235
+ if (!U.record(binding) || binding.view !== paths[0] || !U.record(corpus) || corpus.complete !== true
236
+ || !Array.isArray(corpus.paths) || U.stable(corpus.paths) !== U.stable(expectedPaths)
237
+ || !U.record(corpus.digests) || U.stable(Object.keys(corpus.digests).sort()) !== U.stable(expectedPaths)
238
+ || !expectedPaths.every(p => artifacts[p] && corpus.digests[p] === artifacts[p].digest)) reject('corpus-mismatch');
239
+ if (binding?.baselineDigest !== snapshot.identity.baselineDigest
240
+ || binding?.candidateDigest !== snapshot.identity.candidateDigest
241
+ || binding?.configIdentityDigest !== snapshot.identity.configDigest) reject('snapshot-changed');
242
+ const configSource = snapshot.config.capture.sources.find(source => source.path === binding?.config?.path);
243
+ if (!configSource || configSource.presence !== 'present'
244
+ || U.sha(U.bytes(configSource.bytes)) !== binding?.config?.digest) reject('config-capture-mismatch');
245
+ const generated = binding?.generation?.outputDigest;
246
+ const target = binding?.comparison?.targetDigest;
247
+ if (binding?.generation?.state !== 'pass' || binding?.comparison?.state !== 'equal'
248
+ || !U.text(generated) || generated !== target || !artifacts[binding.view]
249
+ || artifacts[binding.view].digest !== target) reject('derived-binding-mismatch');
250
+ }
229
251
  if (event?.kind === 'review') {
230
252
  if (event.verdict !== 'approve' || !U.text(event.summary)) reject('event-invalid');
231
253
  const principals = context?.principals;
@@ -0,0 +1,24 @@
1
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"cat /sanitized/run8/stage1.txt"},"tool_use_id":"<tool-use-1>","event":"PreToolUse"}
2
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"cat /sanitized/run8/stage1.txt"},"tool_response":{"stdout":"STAGE1 READY\nOutline file: /sanitized/run8/project/docs/adr/VERI-001.md\nInventory file: /sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md\nReviewer must end its reply with exactly these four lines:\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI.","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"tool_use_id":"<tool-use-1>","duration_ms":415,"event":"PostToolUse"}
3
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Agent","tool_input":{"description":"Review outline","prompt":"STAGE1 READY\nOutline file: /sanitized/run8/project/docs/adr/VERI-001.md\nInventory file: /sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md\nReviewer must end its reply with exactly these four lines:\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI.\n\nReview the outline against the inventory row","subagent_type":"outline-reviewer","run_in_background":false},"tool_use_id":"<tool-use-2>","event":"PreToolUse"}
4
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","agent_id":"<agent-1>","agent_type":"outline-reviewer","event":"SubagentStart"}
5
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-reviewer","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md"},"tool_use_id":"<tool-use-3>","event":"PreToolUse"}
6
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-reviewer","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md"},"tool_response":{"type":"text","file":{"filePath":"/sanitized/run8/project/docs/adr/VERI-001.md","content":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Outline\n- Context: verification is recorded as observed, timed evidence (verify-gate).\n- Decision: TBD\n","numLines":13,"startLine":1,"totalLines":13}},"tool_use_id":"<tool-use-3>","duration_ms":2,"event":"PostToolUse"}
7
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-reviewer","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md"},"tool_use_id":"<tool-use-4>","event":"PreToolUse"}
8
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-reviewer","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md"},"tool_response":{"type":"text","file":{"filePath":"/sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md","content":"# Report INV — existing-mechanism inventory for retroactive ADRs\n\nrun id `e1-inv-1` · packet rev 1 · input SHA `0ef0541c6cae550e8269ba712e03797e0e987dfb` (verified: `git rev-parse HEAD` — **executed**).\nFacts only, no decisions. Issue states: **executed** via read-only `gh issue view N --json state` (57 numbers queried 2026-10-27). File/test/issue-number mapping: **source-traced** (grep of `ci/`, `docs/*.md`, `git log --oneline --all --grep`, code citations). ADR shape: **source-traced** (code citations, no execution). Status flags: my classification of those facts.\n\n## 1. ADR shape — the exact minimum file this repo's checker accepts\n\nChecker: `harness-everything/scripts/doc-lifecycle.js` (run via `doc-check.js`; config from `project-docs-resolver.js`). `describes:` is **not** recognized by `doc-lifecycle.js` (absent — grep, **source-traced**).\n\n| Rule | Value | Citation |\n| --- | --- | --- |\n| ID regex | `/^([A-Z][A-Z0-9]{1,9})-(\\d{3,})$/` — domain 2–10 uppercase alnum chars, number ≥ 3 digits | doc-lifecycle.js:20 |\n| ID required | `missing-id` when `id` absent/empty | doc-lifecycle.js:1075–1076 |\n| ID malformed | `invalid-id` when regex fails | doc-lifecycle.js:1077 |\n| Domain declared | `unknown-domain` unless domain ∈ `config.domains` (set from config, doc-lifecycle.js:1035; never hardcoded) | doc-lifecycle.js:1078 |\n| Status required | `missing-status` / `invalid-status` vs `ROLE_STATUSES.adr` | doc-lifecycle.js:1070–1073 |\n| ADR status vocabulary | exactly `proposed` / `accepted` / `rejected` / `superseded` | doc-lifecycle.js:14 |\n| Frontmatter `role` | optional; if present must equal path role (`role-mismatch`) | doc-lifecycle.js:1063–1064 |\n| Frontmatter fields recognized | `id`, `role`, `status`, `aliases`, `supersedes`, `conflicts_with` (+ PRD-only fields); leading `---` YAML subset, no tabs | doc-lifecycle.js:119–147, 1082–1092, 1210–1221, 1315–1321 |\n| `aliases[]` | strings matching ID pattern; no `legacy:` prefix, not self (`invalid-alias`); collisions → `alias-collision` | doc-lifecycle.js:1082–1092, 1159+ |\n| `supersedes` | list of resolvable refs; effective edge requires target `status: superseded`; self/cycle → `supersede-cycle` | doc-lifecycle.js:1210–1221, 1259–1268 |\n| `conflicts_with` | refs must resolve or stay `recorded-conflict` until an effective `supersedes` | doc-lifecycle.js:1315–1321 |\n| Location | **config-driven, not hardcoded**: file must match a `governed` glob entry carrying `role: \"adr\"` (collection doc-lifecycle.js:295–300; `roleAtPath` 2147–2152; no `docs/adr` hardcode in the checker). Resolver infers `docs/adr`/`docs/decisions` only when *generating* config | project-docs-resolver.js:578,599 |\n| Ratchet | schema checks apply to governed files named in `changedPaths`; full mode only with `--all` | docs/document-lifecycle.md:132 |\n\nMinimum accepted new ADR (given a config that declares the domain and an ADR glob):\n\n```markdown\n---\nid: ROUTE-001\nstatus: accepted\n---\n\n# ROUTE-001 — <title>\n\n## Decision\n<text>\n```\n\nThat is the whole required shape: `id` + `status` (+ declared domain). `Decision`-section presence matters only for D6.5 normative-trigger classification, not lint. Pending/edge extras: optional `role`, `aliases`, `supersedes` (with target `superseded`), `conflicts_with`.\n\n## 2. Mechanism inventory\n\nPath abbreviations: **H/** `hooks/scripts/`, **R/** `harness-everything/scripts/`, **C/** `ci/`. States: `M`=merged PR, `C`=closed, `O`=open. All issue states **executed**; attributions **source-traced**.\n\n| # | Mechanism | Decision it embodies | Implementing file(s) | Tests (grep C/) | Issues (state) | Flag |\n| --- | --- | --- | --- | --- | --- | --- |\n| 01 | Kernel routing | Route before execute: tier/strategy/invariants before mutation | R/kernel-router.js, R/tier-router.js, R/router-contract.js, R/routing-keywords.json | C/invariant-routing, C/router-workflow-plan, C/router-phase2-safety, C/prompt-contract-regressions, C/skill-routing-check | #142 (M), #217 (C) | CONSISTENT |\n| 02 | Step-scoped binding resolution | Disclose only active-step knowledge; unknown stays visible | R/kernel-router.js, H/lib/binding-resolver.js, R/kernel-router-core.js | C/mechanism-2ar-binding-resolution, C/invariant-routing | #297 (C) | CONSISTENT |\n| 03 | Workflow lifecycle evidence | Objective evidence before completion; explicit dispositions | H/workflow-stop-gate.js, H/workflow-disposition.js, H/lib/workflow-obligations.js, H/lib/fable-contracts.js | C/mechanism-2an, C/mechanism-2ah, C/mechanism-2as, C/mechanism-2az, C/workflow-execution-contract | #143 (M), #242 (C) | CONSISTENT |\n| 04 | Worktree/Fable isolation | Isolate broad mutation before it starts | H/lib/workflow-isolation.js, R/kernel-router.js | C/mechanism-2af, C/mechanism-2ai, C/mechanism-34-worktree-submodule-reachability | #150,#152,#154,#156,#158 (all M) | CONSISTENT |\n| 05 | Harness Status protocol | One user-visible status block for non-trivial work (semantic, no lock) | skill text (routing docs); no dedicated runtime file | C/reference-check only (no mechanism test) | NO-ISSUE found | NO-TEST |\n| 06 | Action gate | Explicit approval for destructive/external side effects (hard block) | H/action-gate.js, H/action-gate-rules.json, H/codex-action-gate-pre.js, H/codex-action-gate-post.js, H/codex-permission-request.js | C/action-gate, C/mechanism-2ad, C/mechanism-2aa, C/mechanism-2au, C/action-gate-platform-doc | #144 (M); live approval matrix #100 (C) | CONSISTENT |\n| 07 | Rule-of-3 / zoom-out | Stop after 3 same-signature failures; hard boundary | H/rule-of-3.js, H/rule-of-3-tracker.js, H/reset-circuit-breaker.js | C/mechanism-2a-rule-of-3, C/self-evolve-loop | #201 (C) | CONSISTENT |\n| 08 | Stop gate | No completion without observed successful verification | H/stop-gate.js | C/mechanism-2f-stop-gate | #190 (C) | CONSISTENT |\n| 09 | Advisory guard trio | Warn about broad searches, blind overwrites, context pressure (fail-open) | H/boundary-guard.js, H/depth-guard.js, H/context-compact.js | C/mechanism-2b (boundary only); depth/context **no test** | #190 (C) | NO-TEST (2 of 3) |\n| 10 | State persistence / WAL | Persist tool outcomes, mutations, verification milestones | H/state-persist.js, H/lib/harness-state.js | C/mechanism-2c, C/mechanism-2ah, C/telemetry | #160 (M), #147 (M) | CONSISTENT |\n| 11 | Subagent scope guard + contract test | Snapshot/diff around subagent bursts; inter-agent contract testing | H/subagent-scope-guard.js, H/contract-test.js | C/mechanism-2e, C/workflow-plan-consumers, C/workflow-plan-consumers-edge | NO-ISSUE found | NO-ISSUE |\n| 12 | Memory capability token | One-time workflow-bound capability for memory writes | R/kernel-router.js (`issueMemoryCapability`) | **no function-level test** (adjacent: C/phase4-ensemble-memory) | #145 (M) | NO-TEST |\n| 13 | Ensemble policy | MAY-level multi-agent composition under high uncertainty | R/ensemble-policy.js | C/phase4-ensemble-memory | NO-ISSUE found | NO-ISSUE |\n| 14 | Bootstrap / self-repo guard / self-heal | Session bootstrap, env detection, environment repair | R/bootstrap.js, R/self-heal.js, R/runtime-paths.js | C/mechanism-2m-self-repo-guard, C/mechanism-2an-environment-detection-readonly | NO-ISSUE found | NO-ISSUE |\n| 15 | Contract-integrity audit | Prove artifact↔spec traceability | contract-integrity/scripts/audit.js | C/contract-integrity.test | #84 (C), #58 (C) | CONSISTENT |\n| 16 | Verify gate | Treat verification as observed, timed evidence | R/verify-gate.js | C/mechanism-35-verification-contract, C/mechanism-36-verify-timeout | #242 (C) | CONSISTENT |\n| 17 | verify:all aggregate gate | One CI-ordered gate list with PASS/FAIL/UNCHECKED report | scripts/verify-all.js | C/mechanism-38-verify-all (self-regression subset) | NO-ISSUE found | CONSISTENT (see Open Q1) |\n| 18 | Paired behavioral / benchmark evidence | No \"improved\" claim without paired baseline-vs-treatment evidence | behavioral-evals/run.js, benchmarks/run.js, C/ab-test-harness.js | C/mechanism-2j, C/mechanism-32, C/mechanism-335, C/mechanism-2v | #71 (**O**), #130 (M), #242 (C) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 19 | Doc config resolution (D5) | Committed host-neutral config wholly overrides host manifests; AGENTS.md never config | R/project-docs-resolver.js, R/doc-check.js | C/mechanism-2aw, C/reference-check | #325 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 20 | ADR/REQ identity + supersession + index (D6.1–D6.4) | Domain-scoped IDs, aliases, legacy refs, generated index | R/doc-lifecycle.js | C/mechanism-2ax, C/reference-check | #325 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 21 | PRD revision contract (D6.9) | Effective PRD = frozen revision chain with approval record | R/doc-lifecycle.js | C/mechanism-2ay | #325 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 22 | `doc-change` step type | Explicit doc-edit step type, no implicit TDD binding | H/lib/step-types.json, H/lib/workflow-obligations.js | C/mechanism-2az-step-type-registry | #326 (C), #328 (M) | CONSISTENT |\n| 23 | Doc obligations + evidence producer (3a) | Fail-closed doc MUSTs with trusted-producer identity binding | R/doc-obligations.js, R/doc-evidence.js, R/doc-evidence-capture.js | C/mechanism-325-3a-doc-obligations, C/mechanism-325-3a-doc-evidence, C/mechanism-335-baseline-evidence | #334 (M), #335 (C, fixed by #342 — executed state for #342 not queried, result merged) | CONSISTENT |\n| 24 | Reconciliation pilot S1/F4 | Default-OFF opt-in gate; limited credit for `doc-lint`/`link-check` only | H/lib/doc-reconciliation-*.js, H/codex-user-prompt.js (switch) | C/mechanism-325-3b-preparation, C/mechanism-325-3b-s1-review, C/mechanism-341-opencode-lane | #341 (**O**), #350 (**O**), #358 (M), #375 (M: E1 zero qualifying cycles) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 25 | Shared doc craft + derived views (F2/F3) | Regenerate-equals for authorized generators; shared references | R/doc-obligations.js, harness-everything/references/documentation/_shared/ | C/mechanism-325-f3-derived-views, C/reference-check | #356 (M), #357 (M) | CONSISTENT |\n| 26 | #320 obligation abstraction (bounded consumer) | Doc workflow reconciliation consumes workflow abstractions; broad promotion gated | H/lib/doc-reconciliation-core.js + R/doc-obligations.js | same as #24 tests | #320 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 27 | System One observation capture | Local, content-private turn observation; fail-open, silent | H/observation-hook.js, hooks/scripts/lib/telemetry.js | C/mechanism-34-observations, C/mechanism-2am-observations-session-import | #233 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 28 | System One engine (tier-4 experimental) | Learned intent routing + resident/eval pipeline | scripts/system-one-*.js/py, R/system-one/ (install) | C/mechanism-system-one-*.test.js suite; ci.yml Windows S1 lanes | #233 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 29 | S1 intent/suggestion/noise gates | Weak-intent gating with Laya teacher; `rule_v2` noise gate | docs-system-one-* scripts (corpus/evaluate/label) | C/mechanism-system-one-evaluation, C/system-one-*.py | #255 (**O**), #265 (**O**), #297 (C) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 30 | Skill telemetry | Privacy-safe skill load start/close telemetry window | H/telemetry-adapter.js, H/lib/telemetry.js, telemetry/scripts/ | C/telemetry.test | #147 (M), #37 (C), #122 top-level blocker (**O**) | CONSISTENT |\n| 31 | OpenCode plugin | Project-scope `.js` loading + edit/verify state on OpenCode 1.18.31 (macOS, retained evidence only) | opencode-plugin/ | C/mechanism-2n, C/mechanism-30-loadability, C/mechanism-31-hardlock-evidence, C/mechanism-2p, C/mechanism-39, C/mechanism-341-opencode-lane | #128 (M), #129 (M), #82 (**O**) | **ISSUE-OPEN-BUT-SHIPPED** (live claim bounded) |\n| 32 | Codex plugin hooks compat | Codex hook adapters/package-level parity with Claude hooks | H/lib/platforms/codex.js, scripts/codex-hooks-compat.js | C/mechanism-2al, C/mechanism-2au, C/mechanism-2ao | #110 (**O**), #122 (**O**), #135 (C), #98 (M), #109 (C) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 33 | Platform adapter matrix | Per-host capability/evidence claims centralized and claimed as tested contract | H/lib/platforms/ (claude/codex/cursor/copilot/continue/hermes), docs/platform-capabilities.md | C/mechanism-2w-platform-compatibility, C/mechanism-2t-platform-skill-targets, C/mechanism-2g-platform-ignore | #82 (**O**), #123 (M), #127 (C), #135 (C) | **ISSUE-OPEN-BUT-SHIPPED** |\n| 34 | Plugin packaging (Claude + OpenAI + submission) | Generated distribution manifests are facts, byte-consistent copies | scripts/stage-claude-plugin.js, scripts/validate-claude-plugin.js, scripts/sync-openai-plugin.js, scripts/build-openai-submission.js, .claude-plugin/ | C/mechanism-2z, C/openai-plugin-package, C/openai-plugin-submission, C/openai-plugin-runtime, C/mechanism-packaging-bytecode | #123 (M), #128 (M), #129 (M) | CONSISTENT |\n| 35 | Release automation | semantic-release owns SemVer; nested versions synced; skip-CI version bumps | scripts/sync-release-version.js, .github/workflows/release.yml, scripts/generate-sbom.js | C/mechanism-2x (×2), C/mechanism-2y, C/mechanism-2ap-sbom, C/mechanism-2u-issue-44, C/semantic-release-analyzer | #20 (C) | CONSISTENT |\n| 36 | Manifest sync across hosts | All distribution manifests generated from canonical skills | scripts/plugin-sync.js (.sh/.ps1), scripts/repository-contract.js | C/mechanism-2ao, C/mechanism-2ab, C/repository-contract.test | #123 (M) | CONSISTENT |\n| 37 | Installer | Interactive/non-interactive install, verify, uninstall, shared store | scripts/installer.js, bin/cli.js | C/installer-roundtrip, C/mechanism-2h, C/mechanism-2i (×2), C/mechanism-2p-installer-links, C/mechanism-2q, C/mechanism-2r | NO-ISSUE found | NO-ISSUE |\n| 38 | Skill routing evals / collision gate | Every routed skill has a trigger eval; descriptions must not collide | evals/<skill>/eval.yaml, C/description-collision.js, C/skill-routing-check.js | (these files are the gates) | NO-ISSUE found | CONSISTENT (see Open Q1) |\n| 39 | Commit discipline | Conventional commits enforced locally via husky+commitlint | commitlint.config.js, .husky/ | C/mechanism-33-issues-205-207, C/mechanism-2x-release-commit-message | #205 (C), #207 (C) | CONSISTENT |\n\nCounts: 39 rows · CONSISTENT 21 · ISSUE-OPEN-BUT-SHIPPED 11 · NO-ISSUE 5 · NO-TEST 2 (02 rows flagged NO-TEST cover 3 sub-mechanisms).\n\n## 3. Area grouping + candidate domain prefixes\n\nFormats follow `^[A-Z][A-Z0-9]{1,9}$` (checked against doc-lifecycle.js:20 ADR_ID domain form). No domains are declared today (`.agents/harness-everything/project-docs.json` governs two `reference` files only, no `domains` key) — so every candidate is new, and adopting any requires editing `project-docs.json`, which under #325 D6.8 is the project's own declared list (Harness never hard-codes it).\n\n| Area | Rows | Candidate prefix | Why |\n| --- | --- | --- | --- |\n| Routing & workflow contract | 01–05 | `ROUTE` | Kernel routing / topology / bindings are one decision family |\n| Enforcement & safety boundaries | 06–15 | `ENFC` | Approval, breaker, stop evidence, persistence — the hook-path contract |\n| Verification & evidence | 16–18, 15 | `VERI` | Shared \"objective evidence before claiming done\" decision |\n| Document lifecycle (#325) | 19–26 | `DOCS` | Already contract-scoped to #325; natural domain |\n| System One (experimental) | 27–29 | `SONE` | Clearly-labeled tier-4 experimental family |\n| Platform, packaging, release | 30–39 | `DIST` | Distribution/platform claims and tooling |\n\nZero-commit numbers (#71, #84, #109, #110, #122, #127, #135, #217, #350 — doc-referenced only in this clone) are kept **with state flags** rather than dropped, so a future ADR citing them shows they lack commit-message anchors in this SHA's history.\n\n**Single-domain alternative:** one prefix `HARN` for every retroactive ADR (Harness-wide decisions), splitting later via `supersedes`/aliases once areas stabilize. Cheaper to ship (one `domains` entry, no per-area risk of overlap collisions); loses the ability to grep decisions by area from the ID alone.\n\n## 4. Mismatch list (non-CONSISTENT rows) — the \"功能與 issues 狀態\" findings\n\n1. **depth-guard.js, context-compact.js — NO-TEST.** Shipped on the hook path (`hooks/hooks.json:99–115`) under closed issue #190, but their basenames appear nowhere in `ci/` outside `archive/`. Candidate: ISSUE-CLOSED-BUT-MISSING *test coverage*, not mechanism.\n2. **memory capability token (kernel-router.js#issueMemoryCapability) — NO-TEST** despite merged #145. Function itself untested; only adjacent ensemble-memory policy tests exist.\n3. **Harness Status protocol (row 05) — NO-TEST.** Mandated in AGENTS.md #11 and docs/routing.md; no mechanism test exists (it is semantic, not enforced — #11 itself says \"Not a hard lock\").\n4. **#71 OPEN vs shipped policy.** architecture.md and the validation boundary require paired behavioral evidence for lifecycle-effectiveness claims — the requirement is live and unmet while the machinery (rows 18, 24) ships around it: the classic \"shipped, gate pending\" split. #375 (merged) reports E1 with zero qualifying cycles.\n5. **#341/#350 OPEN vs shipped default-OFF pilot.** S1 + F4 shipped as opt-in (`HARNESS_DOC_RECONCILIATION_PILOT=1`) with fail-closed withholding; expansion explicitly not approved (E1 gate). Consistent-with-docs but issue-open → flagged.\n6. **#82/#110/#122 OPEN: live-host verification umbrella.** platform-capabilities claims are held to mechanism-package evidence; live-host firing is still open for Codex (#110, #122 mount/attribution blocker) and the #82 compatibility program. All OpenCode/Codex live rows inherit this.\n7. **#233/#255/#265 OPEN vs shipped System One.** Experimental engine, gates and Windows CI lanes are live in `ci.yml` while their governing experiment issues are open — intentional (labeled experimental) but must be reflected, not hidden, in the ADR set.\n8. **#325 OPEN vs merged doc-lifecycle slices.** The owner-decision umbrella (#325) stays open by design; all shipped phases (#327/#334/#356/#357/#358, …) flag ISSUE-OPEN-BUT-SHIPPED. Not a defect — a structural fact for the retroactive ADRs' status choices.\n9. **#201 as Rule-of-3 anchor (row 07) is the only issue found** — \"advisory re-trip contract (#201)\" from this clone's log. Earlier origin of the mechanism may be unreferenced; treating #201 as governing is an inference (label: source-traced, weak).\n\n## 5. Open questions\n\n1. **Q1 — CI routing-gate coverage (inconclusive):** `package.json` exposes `test:routing:skills`/`test:routing:invariants` and `scripts/verify-all.js` runs them, but `.github/workflows/ci.yml` jobs do not invoke them by name (consistency job runs `test:consistency`, `test:references`, `test:release`, `test:collision`, `test:plugin:openai`, `test:plugin:submission`, `test:platform:compatibility`, `test:portability`; self-regression job runs `npm test` + `test:mutations`). Whether `npm test` self-regression reaches the routing tests was not traced end to end — rows 01 and 38 keep CONSISTENT subject to Q1.\n2. **Q2 — ADR scope boundary:** does \"已經存在的機制\" (existing mechanisms) cover semantic/instruction-only contracts (rows 05, plus skill-text obligations), or only runtime code with hooks/tests? Recommendation-shaped, not a fact: mechanism code first.\n3. **Q3 — domain assignment ownership:** adopting `ROUTE/ENFC/…` (or `HARN`) requires a `project-docs.json` `domains` edit + ADR glob + `project-overrides-host` warning reconciliation under D5; owner decides under #325.\n4. **Q4 — #342 executed state:** #335's own state was queried (closed, executed) but the first fix PR #342's state was not queried in the batch (its merge is source-traced from docs only).\n5. **Q5 — alias stability:** issues with zero commit-message matches in this clone (71, 84, 109, 110, 122, 127, 135, 217, 350) may exist in other branches/clone histories; anchor-richness is lower than for #233/#325.\n\n<!-- harness-label {\"judgement\":\"facts-only inventory report per packet; no repo writes\",\"risk\":\"none\"} -->\nSTATUS: DONE\n","numLines":129,"startLine":1,"totalLines":129}},"tool_use_id":"<tool-use-4>","duration_ms":1,"event":"PostToolUse"}
9
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-1>","agent_type":"outline-reviewer","effort":{"level":"medium"},"stop_hook_active":false,"agent_transcript_path":"/sanitized/run8/agent-transcripts/transcript-1.jsonl","last_assistant_message":"The outline passes all four checks against the inventory.\n\n- **Inventory row:** The outline cites \"Inventory row: 16\". Row 16 in the inventory is \"Verify gate\", \"Treat verification as observed, timed evidence\", implemented in `R/verify-gate.js`. The outline's context line matches it.\n- **Target path:** `docs/adr/VERI-001.md` is the file reviewed. Its frontmatter `id: VERI-001` fits the checker's ID pattern, and `status: proposed` is an allowed ADR status.\n- **Domain:** The outline says \"Domain: VERI\". Section 3 of the inventory assigns rows 16–18 and 15 to the candidate prefix `VERI`.\n- **Decision:** The line reads \"Decision: TBD\", so there is no substantive Decision yet.\n\nI did not compute the SHA-256 hashes. The two `Reviewed artifact` lines below use the values you gave me unchanged.\n\nFiles reviewed:\n- `/sanitized/run8/project/docs/adr/VERI-001.md`\n- `/sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md`\n\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI.","background_tasks":[],"session_crons":[],"event":"SubagentStop"}
10
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Agent","tool_input":{"description":"Review outline","prompt":"STAGE1 READY\nOutline file: /sanitized/run8/project/docs/adr/VERI-001.md\nInventory file: /sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md\nReviewer must end its reply with exactly these four lines:\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI.\n\nReview the outline against the inventory row","subagent_type":"outline-reviewer","run_in_background":false},"tool_response":{"status":"completed","prompt":"STAGE1 READY\nOutline file: /sanitized/run8/project/docs/adr/VERI-001.md\nInventory file: /sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md\nReviewer must end its reply with exactly these four lines:\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI.\n\nReview the outline against the inventory row","agentId":"<agent-1>","agentType":"outline-reviewer","toolsUsed":["Read"],"harnessNoteCount":1,"harnessTailCount":0,"harnessSectionHash":"510238c68f070d12","content":[{"type":"text","text":"<tools-used-before-summary>Read</tools-used-before-summary>\n"},{"type":"text","text":"The outline passes all four checks against the inventory.\n\n- **Inventory row:** The outline cites \"Inventory row: 16\". Row 16 in the inventory is \"Verify gate\", \"Treat verification as observed, timed evidence\", implemented in `R/verify-gate.js`. The outline's context line matches it.\n- **Target path:** `docs/adr/VERI-001.md` is the file reviewed. Its frontmatter `id: VERI-001` fits the checker's ID pattern, and `status: proposed` is an allowed ADR status.\n- **Domain:** The outline says \"Domain: VERI\". Section 3 of the inventory assigns rows 16–18 and 15 to the candidate prefix `VERI`.\n- **Decision:** The line reads \"Decision: TBD\", so there is no substantive Decision yet.\n\nI did not compute the SHA-256 hashes. The two `Reviewed artifact` lines below use the values you gave me unchanged.\n\nFiles reviewed:\n- `/sanitized/run8/project/docs/adr/VERI-001.md`\n- `/sanitized/run8/state/workspaces/project-c3344fec53f4/state/sessions/<session-1>/doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md`\n\nReview verdict: approve\nReviewed artifact: docs/adr/VERI-001.md sha256:7e03847cc8ceae735bfb76d85f4fcdf09ea0cd6e84299405ee44733e4917b6b8\nReviewed artifact: doc-reconciliation/outline-confirmation/inventory/2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6.md sha256:2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6\nSummary: Approved inventory row 16 for docs/adr/VERI-001.md in domain VERI."}],"resolvedModel":"claude-sonnet-5-5","totalDurationMs":12002,"totalTokens":14638,"totalToolUseCount":2,"usage":{"output_tokens_details":{"thinking_tokens":85},"input_tokens":2,"cache_creation_input_tokens":10723,"cache_read_input_tokens":2956,"output_tokens":957,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":10723},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":957,"cache_read_input_tokens":2956,"cache_creation_input_tokens":10723,"cache_creation":{"ephemeral_5m_input_tokens":10723,"ephemeral_1h_input_tokens":0},"type":"message"}],"speed":"standard","fallback_credit":null},"toolStats":{"readCount":2,"searchCount":0,"bashCount":0,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0},"canContinueAgent":true},"tool_use_id":"<tool-use-2>","duration_ms":12003,"event":"PostToolUse"}
11
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"cat /sanitized/run8/stage2.txt"},"tool_use_id":"<tool-use-5>","event":"PreToolUse"}
12
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"cat /sanitized/run8/stage2.txt"},"tool_response":{"stdout":"STAGE2 READY\nWorker prompt (first line verbatim):\nHARNESS_OUTLINE_DISPATCH_V1 taskId=task-veri16+verify attemptId=attempt-1 token=sanitized-dispatch-token-1 workerRoot=/sanitized/run8/project","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"tool_use_id":"<tool-use-5>","duration_ms":12,"event":"PostToolUse"}
13
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Agent","tool_input":{"description":"Draft ADR","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-veri16+verify attemptId=attempt-1 token=sanitized-dispatch-token-1 workerRoot=/sanitized/run8/project\nDraft the ADR as instructed.","subagent_type":"outline-drafter","run_in_background":false},"tool_use_id":"<tool-use-6>","event":"PreToolUse"}
14
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","agent_id":"<agent-2>","agent_type":"outline-drafter","event":"SubagentStart"}
15
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md"},"tool_use_id":"<tool-use-7>","event":"PreToolUse"}
16
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Read","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md"},"tool_response":{"type":"text","file":{"filePath":"/sanitized/run8/project/docs/adr/VERI-001.md","content":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Outline\n- Context: verification is recorded as observed, timed evidence (verify-gate).\n- Decision: TBD\n","numLines":13,"startLine":1,"totalLines":13}},"tool_use_id":"<tool-use-7>","duration_ms":1,"event":"PostToolUse"}
17
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Write","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md","content":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Context\nWithout a shared standard, claims that work is verified can rest on anything from a test run to a quick read of the code.\n\n## Decision\nVerification is accepted only as observed, timed command evidence, meaning a command that was actually run with its output and duration recorded. Claims made from reading code are not verification.\n"},"tool_use_id":"<tool-use-8>","event":"PreToolUse"}
18
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Write","tool_input":{"file_path":"/sanitized/run8/project/docs/adr/VERI-001.md","content":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Context\nWithout a shared standard, claims that work is verified can rest on anything from a test run to a quick read of the code.\n\n## Decision\nVerification is accepted only as observed, timed command evidence, meaning a command that was actually run with its output and duration recorded. Claims made from reading code are not verification.\n"},"tool_response":{"type":"update","filePath":"/sanitized/run8/project/docs/adr/VERI-001.md","content":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Context\nWithout a shared standard, claims that work is verified can rest on anything from a test run to a quick read of the code.\n\n## Decision\nVerification is accepted only as observed, timed command evidence, meaning a command that was actually run with its output and duration recorded. Claims made from reading code are not verification.\n","structuredPatch":[{"oldStart":7,"oldLines":6,"newStart":7,"newLines":8,"lines":[" Target: docs/adr/VERI-001.md"," Domain: VERI"," ","-## Outline","-- Context: verification is recorded as observed, timed evidence (verify-gate).","-- Decision: TBD","+## Context","+Without a shared standard, claims that work is verified can rest on anything from a test run to a quick read of the code.","+","+## Decision","+Verification is accepted only as observed, timed command evidence, meaning a command that was actually run with its output and duration recorded. Claims made from reading code are not verification."]}],"originalFile":"---\nid: VERI-001\nstatus: proposed\n---\n\nInventory row: 16\nTarget: docs/adr/VERI-001.md\nDomain: VERI\n\n## Outline\n- Context: verification is recorded as observed, timed evidence (verify-gate).\n- Decision: TBD\n","userModified":false},"tool_use_id":"<tool-use-8>","duration_ms":2,"event":"PostToolUse"}
19
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"git add docs/adr/VERI-001.md"},"tool_use_id":"<tool-use-9>","event":"PreToolUse"}
20
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"git add docs/adr/VERI-001.md"},"tool_response":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"tool_use_id":"<tool-use-9>","duration_ms":15,"event":"PostToolUse"}
21
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"git commit -q -m \"D: draft VERI-001\""},"tool_use_id":"<tool-use-10>","event":"PreToolUse"}
22
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"tool_name":"Bash","tool_input":{"command":"git commit -q -m \"D: draft VERI-001\""},"tool_response":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"tool_use_id":"<tool-use-10>","duration_ms":23,"event":"PostToolUse"}
23
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","agent_id":"<agent-2>","agent_type":"outline-drafter","effort":{"level":"medium"},"stop_hook_active":false,"agent_transcript_path":"/sanitized/run8/agent-transcripts/transcript-2.jsonl","last_assistant_message":"worker done.","background_tasks":[],"session_crons":[],"event":"SubagentStop"}
24
+ {"session_id":"<session-1>","cwd":"/sanitized/run8/project","prompt_id":"<prompt-1>","permission_mode":"default","effort":{"level":"medium"},"tool_name":"Agent","tool_input":{"description":"Draft ADR","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-veri16+verify attemptId=attempt-1 token=sanitized-dispatch-token-1 workerRoot=/sanitized/run8/project\nDraft the ADR as instructed.","subagent_type":"outline-drafter","run_in_background":false},"tool_response":{"status":"completed","prompt":"HARNESS_OUTLINE_DISPATCH_V1 taskId=task-veri16+verify attemptId=attempt-1 token=sanitized-dispatch-token-1 workerRoot=/sanitized/run8/project\nDraft the ADR as instructed.","agentId":"<agent-2>","agentType":"outline-drafter","toolsUsed":["Bash","Read","Write"],"harnessNoteCount":1,"harnessTailCount":0,"harnessSectionHash":"f724eae3efefe206","content":[{"type":"text","text":"<tools-used-before-summary>Write</tools-used-before-summary>\n"},{"type":"text","text":"worker done."}],"resolvedModel":"claude-sonnet-5-5","totalDurationMs":16376,"totalTokens":6343,"totalToolUseCount":4,"usage":{"output_tokens_details":{"thinking_tokens":0},"input_tokens":2,"cache_creation_input_tokens":79,"cache_read_input_tokens":6256,"output_tokens":6,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":79},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":6,"cache_read_input_tokens":6256,"cache_creation_input_tokens":79,"cache_creation":{"ephemeral_5m_input_tokens":79,"ephemeral_1h_input_tokens":0},"type":"message"}],"speed":"standard","fallback_credit":null},"toolStats":{"readCount":1,"searchCount":0,"bashCount":2,"editFileCount":1,"linesAdded":15,"linesRemoved":0,"otherToolCount":0},"canContinueAgent":true},"tool_use_id":"<tool-use-6>","duration_ms":16376,"event":"PostToolUse"}
@@ -346,7 +346,7 @@ function captureReferences(context) {
346
346
  const taskId = context.workflow.docReconciliationTask?.taskId;
347
347
  const outlineReviews = outlineReviewCaptureRefs(context.sessionDir, taskId);
348
348
  const reviews = captures.filter(item => item && typeof item.ref === 'string' && item.event === 'SubagentStop'
349
- && !outlineReviews.has(item.ref));
349
+ && ['approve', 'reject'].includes(item.reviewVerdict) && !outlineReviews.has(item.ref));
350
350
  const references = [];
351
351
  for (const [index, capability] of ['doc-lint', 'link-check'].entries()) {
352
352
  const ordered = index ? candidates.slice(1).concat(candidates.slice(0, 1)) : candidates;
@@ -355,6 +355,8 @@ function captureReferences(context) {
355
355
  const provider = require('./doc-reconciliation-provider');
356
356
  references.push(...candidates.filter(item => provider.resolveDocCheckInvocation(item.command, item.cwd).conflictAssessment === true)
357
357
  .map(item => `${item.ref}#conflict-check`));
358
+ references.push(...candidates.filter(item => provider.resolveDocCheckInvocation(item.command, item.cwd).derivedView)
359
+ .map(item => `${item.ref}#derived-regenerate`));
358
360
  references.push(...outlineDispatchReferences(captures, taskId, context.sessionDir));
359
361
  // The adapter accepts one event per capability; rotating candidate order lets separate valid runs fill each capability.
360
362
  return [...references, ...reviews.map(item => `${item.ref}#quality-review`)];