harness-everything 0.37.0 → 0.38.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 (31) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +16 -0
  4. package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +70 -11
  5. package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +24 -556
  6. package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +6842 -0
  7. package/ci/fixtures/doc-obligations/reconciliation-s1/fail-session-writes.js +116 -0
  8. package/ci/lib/doc-reconciliation-prep-fixtures.js +116 -10
  9. package/ci/mechanism-325-3b-preparation.test.js +8 -8
  10. package/ci/mechanism-325-3b-s1-review.test.js +831 -0
  11. package/ci/mechanism-325-3b-s1.test.js +188 -0
  12. package/ci/prep/doc-reconciliation-red.js +2 -1
  13. package/docs/doc-workflow-reconciliation.md +217 -26
  14. package/docs/document-lifecycle.md +4 -4
  15. package/harness-everything/SKILL.md +1 -1
  16. package/harness-everything/scripts/doc-lifecycle.js +1 -1
  17. package/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
  18. package/hooks/scripts/lib/workflow-obligations.js +44 -0
  19. package/hooks/scripts/workflow-disposition.js +2 -0
  20. package/hooks/scripts/workflow-stop-gate.js +24 -1
  21. package/opencode-plugin/plugin.json +1 -1
  22. package/package.json +1 -1
  23. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  24. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
  25. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +44 -0
  26. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +2 -0
  27. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +24 -1
  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-lifecycle.js +1 -1
  31. package/sbom.cdx.json +7 -7
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // #325 Phase 3b S1 strict suite: switch, trusted scope and fail-closed no-credit gate (RED).
5
+ //
6
+ // Every assertion in reconciliation-s1/cases.json is a strict S1 contract assertion, run against the real CLI, hook and
7
+ // Stop entry points on both legs (source and packaged) and both modes (OFF and ON with the switch set to exactly "1").
8
+ // Each assertion is tagged s1Kind:
9
+ // retired one of the 26 S1 retire IDs in docs/doc-workflow-reconciliation.md; expected to FAIL on the current base
10
+ // control behaviour that must PASS now and after GREEN (P06, P07u, P02d, OFF legacy, Stop exit 0, switch values)
11
+ // additional a named S1 contract case beyond the retire table (governed scope, direct aggregate, OFF-seeded, ON->OFF->ON)
12
+ // This suite grants no pilot credit and never consults the prep registry: a FAIL here is a real gap, not a qualified one.
13
+ // Infrastructure faults (spawn error, deadline, output bound, exit outside the declared policy, malformed output) are
14
+ // reported as INFRA and are distinct from assertion FAIL. Exit 1 means at least one assertion FAILED; exit 2 means
15
+ // infrastructure. The suite cannot pass until GREEN lands; that is the RED contract.
16
+
17
+ const path = require('path');
18
+ const fx = require('./lib/doc-reconciliation-prep-fixtures');
19
+
20
+ const CORPUS_FILE = path.join(__dirname, 'fixtures', 'doc-obligations', 'reconciliation-s1', 'cases.json');
21
+ const corpus = fx.readCaseCorpus(CORPUS_FILE);
22
+ const LEGS = ['source', 'packaged'];
23
+ const MODES = ['off', 'on'];
24
+ const ALLOWED_OPERATORS = new Set(['equals', 'notEquals', 'includes', 'absent']);
25
+ const S1_KINDS = new Set(['retired', 'control', 'additional']);
26
+ const REQUIRED_CASE_FIELDS = ['id', 'family', 'description', 'topology', 'requirements', 'input', 'actions', 'expectations'];
27
+
28
+ // The doc's "S1 retire" table: 26 IDs × 2 legs, by case. Any drift between this list and the corpus is a failure.
29
+ const RETIRED_BY_CASE = {
30
+ P01: ['verify-exit-refused', 'verify-obligation-pending', 'stop-unsatisfied', 'stop-reports-verification'],
31
+ P04: ['direct-execute-refused', 'direct-verify-refused', 'verify-obligation-pending'],
32
+ P02a: ['verify-pass-exit', 'stop-state', 'stop-reports'],
33
+ P02b: ['verify-pass-exit', 'stop-state', 'stop-reports'],
34
+ P02c: ['verify-pass-exit', 'stop-state', 'stop-reports'],
35
+ P02g: ['verify-pass-exit', 'stop-state', 'stop-reports'],
36
+ P02f: ['verify-pass-exit', 'stop-state', 'stop-reports'],
37
+ P03: ['verify-pass-exit'],
38
+ 'P07-fable-staged': ['pilot-status-unavailable'],
39
+ 'P07-fable-parallel': ['pilot-status-unavailable'],
40
+ 'P07-fable-multi-agent-workspace': ['pilot-status-unavailable']
41
+ };
42
+ const RETIRED_COUNT = Object.values(RETIRED_BY_CASE).reduce((total, ids) => total + ids.length, 0);
43
+
44
+ let passed = 0;
45
+ let failed = 0;
46
+ let infra = 0;
47
+ const failures = [];
48
+ const infrastructure = [];
49
+
50
+ const show = value => (value === undefined ? 'undefined' : JSON.stringify(value));
51
+
52
+ // The reason a FAIL names, in the terms the doc uses: "exit 0 where 2 desired", "state satisfied where running desired".
53
+ function reasonFor(assertion, actual) {
54
+ const last = assertion.path[assertion.path.length - 1];
55
+ const label = ({ exitCode: 'exit', state: 'state', unresolved: 'unresolved', pilotStatus: 'pilot status',
56
+ pilotReportOrigin: 'pilot origin', pilot: 'pilot' })[last]
57
+ || (assertion.path.includes('obligations') && last === 'status' ? 'obligation status' : null)
58
+ || (assertion.path.includes('requirements') && last === 'status' ? 'step status' : null)
59
+ || assertion.path.slice(-2).join('.');
60
+ const absent = actual && typeof actual === 'object' && actual.kind === 'absent';
61
+ if (assertion.operator === 'includes') {
62
+ return absent ? `${label} absent where ${show(assertion.expected)} desired`
63
+ : `${label} ${show(actual)} lacks ${show(assertion.expected)}`;
64
+ }
65
+ if (assertion.operator === 'notEquals') return `${label} ${show(actual)} where a value other than ${show(assertion.expected)} desired`;
66
+ if (assertion.operator === 'absent') return `${label} present where absent desired`;
67
+ if (absent) return `${label} absent where ${show(assertion.expected)} desired`;
68
+ return `${label} ${show(actual)} where ${show(assertion.expected)} desired`;
69
+ }
70
+
71
+ function record(status, id, detail) {
72
+ if (status === 'PASS') passed += 1;
73
+ else if (status === 'FAIL') { failed += 1; failures.push(id); }
74
+ else { infra += 1; infrastructure.push(id); }
75
+ console.log(` ${status} ${id}${detail ? ` — ${detail}` : ''}`);
76
+ }
77
+
78
+ // Parity between the two legs for one replay, mirroring the prep runner's comparable() view.
79
+ function comparable(root) {
80
+ const entries = Object.entries(root.actions).map(([id, action]) => [id, {
81
+ exitCode: action.exitCode,
82
+ signal: action.signal,
83
+ stdout: action.json === null ? action.stdout : null,
84
+ json: action.json,
85
+ stderr: action.stderr,
86
+ state: action.state
87
+ }]);
88
+ return fx.normalize(Object.fromEntries([...entries, ['$final', root.state]]));
89
+ }
90
+
91
+ console.log('=== #325 3b S1: strict switch, trusted scope and fail-closed no-credit suite (RED) ===');
92
+
93
+ // ---- Inventory: the corpus is well formed and marks exactly the doc's retire table -----------------------------
94
+ {
95
+ const problems = [];
96
+ const caseIds = corpus.cases.map(item => item.id);
97
+ if (new Set(caseIds).size !== caseIds.length) problems.push('case ids are not unique');
98
+ for (const item of corpus.cases) {
99
+ for (const field of REQUIRED_CASE_FIELDS) {
100
+ if (item[field] === undefined) problems.push(`${item.id} lacks ${field}`);
101
+ }
102
+ const actionIds = item.actions.map(action => action.id);
103
+ if (new Set(actionIds).size !== actionIds.length) problems.push(`${item.id} action ids are not unique`);
104
+ for (const action of item.actions) {
105
+ if (!fx.ALL_OPS.has(action.op)) problems.push(`${item.id}.${action.id} op ${action.op} is not allowed`);
106
+ for (const mode of MODES) {
107
+ try { fx.exitPolicy(action, mode); } catch (error) { problems.push(error.message); }
108
+ }
109
+ }
110
+ for (const mode of MODES) {
111
+ const ids = item.expectations[mode].map(assertion => assertion.id);
112
+ if (new Set(ids).size !== ids.length) problems.push(`${item.id}.${mode} assertion ids are not unique`);
113
+ for (const assertion of item.expectations[mode]) {
114
+ if (!ALLOWED_OPERATORS.has(assertion.operator)) problems.push(`${item.id}.${assertion.id} operator ${assertion.operator}`);
115
+ if (!Array.isArray(assertion.path) || assertion.path.length === 0) problems.push(`${item.id}.${assertion.id} has no path`);
116
+ if (!S1_KINDS.has(assertion.s1Kind)) problems.push(`${item.id}.${assertion.id} lacks a valid s1Kind`);
117
+ }
118
+ }
119
+ }
120
+ const retired = [];
121
+ for (const item of corpus.cases) {
122
+ for (const assertion of item.expectations.on) {
123
+ if (assertion.s1Kind === 'retired') retired.push(`${item.id}:${assertion.id}`);
124
+ }
125
+ }
126
+ const expected = Object.entries(RETIRED_BY_CASE).flatMap(([caseId, ids]) => ids.map(id => `${caseId}:${id}`));
127
+ const missing = expected.filter(entry => !retired.includes(entry));
128
+ const extra = retired.filter(entry => !expected.includes(entry));
129
+ if (missing.length || extra.length) problems.push(`retired set differs from the doc table: missing ${show(missing)} extra ${show(extra)}`);
130
+ if (expected.length !== RETIRED_COUNT || RETIRED_COUNT !== 26) problems.push(`the doc table names ${RETIRED_COUNT} IDs, not 26`);
131
+ if (problems.length) record('FAIL', 'inventory: corpus is well formed and marks the 26 S1 retire IDs', problems.join('; '));
132
+ else record('PASS', `inventory: corpus is well formed and marks the ${RETIRED_COUNT} S1 retire IDs`);
133
+ }
134
+
135
+ // ---- Replays: every case, both legs, both modes ----------------------------------------------------------------
136
+ for (const item of corpus.cases) {
137
+ for (const mode of MODES) {
138
+ const roots = {};
139
+ for (const leg of LEGS) {
140
+ let root;
141
+ try {
142
+ root = fx.scrubReplay(fx.replayCase(item, leg, mode === 'on' ? { pilot: '1' } : undefined));
143
+ } catch (error) {
144
+ infrastructure.push(`${item.id}.${mode} [${leg}]`);
145
+ infra += 1;
146
+ console.log(` INFRA ${item.id}.${mode} [${leg}] — replay failed: ${error.message.split('\n')[0]}`);
147
+ continue;
148
+ }
149
+ roots[leg] = root;
150
+ const faults = fx.infrastructureFaults(item, root, mode);
151
+ if (faults.length) {
152
+ infra += 1;
153
+ infrastructure.push(`${item.id}.${mode} [${leg}]`);
154
+ for (const fault of faults) console.log(` INFRA ${item.id}.${mode} [${leg}] ${fault.actionId}: ${fault.message}`);
155
+ continue;
156
+ }
157
+ for (const assertion of item.expectations[mode]) {
158
+ const result = fx.evaluateAssertion(root, assertion);
159
+ const id = `${item.id}.${mode}.${assertion.id} [${leg}]`;
160
+ const tag = `(${assertion.s1Kind})`;
161
+ if (result.pass) record('PASS', id, tag);
162
+ else record('FAIL', id, `${tag} ${reasonFor(assertion, result.actual)}`);
163
+ }
164
+ }
165
+ if (roots.source && roots.packaged && !fx.infrastructureFaults(item, roots.source, mode).length
166
+ && !fx.infrastructureFaults(item, roots.packaged, mode).length) {
167
+ const same = JSON.stringify(comparable(roots.source)) === JSON.stringify(comparable(roots.packaged));
168
+ const id = `${item.id}.${mode}.parity [source=packaged]`;
169
+ if (same) record('PASS', id, '(control)');
170
+ else record('FAIL', id, '(control) source and packaged legs disagree');
171
+ }
172
+ }
173
+ }
174
+
175
+ // ---- Summary -----------------------------------------------------------------------------------------------------
176
+ console.log(`\nS1 strict summary: ${passed} PASS, ${failed} FAIL, ${infra} INFRA across ${corpus.cases.length} cases (pilotCredit: false)`);
177
+ if (failures.length) console.log(`FAILED assertions (${failures.length}):\n ${failures.join('\n ')}`);
178
+ if (infrastructure.length) console.log(`INFRASTRUCTURE (${infrastructure.length}):\n ${infrastructure.join('\n ')}`);
179
+
180
+ if (infra > 0) {
181
+ console.error('S1 suite: infrastructure faults present; these are not assertion results.');
182
+ process.exitCode = 2;
183
+ } else if (failed > 0) {
184
+ console.error('S1 suite: assertion FAIL present (expected on the RED base; GREEN must clear every FAIL).');
185
+ process.exitCode = 1;
186
+ } else {
187
+ console.log('S1 suite: all assertions PASS.');
188
+ }
@@ -38,7 +38,8 @@ const ENTRY_KEYS = ['caseId', 'assertionId', 'distribution', 'baselineSha', 'act
38
38
  const FINGERPRINT_FILES = [
39
39
  'hooks/scripts/lib/workflow-obligations.js',
40
40
  'hooks/scripts/workflow-disposition.js',
41
- 'hooks/scripts/workflow-stop-gate.js'
41
+ 'hooks/scripts/workflow-stop-gate.js',
42
+ 'hooks/scripts/lib/doc-reconciliation-pilot.js'
42
43
  ];
43
44
  const CANDIDATE_FILE = 'ci/fixtures/doc-obligations/reconciliation-prep/pending-red.candidate.json';
44
45
  const UNREVIEWED_REASON = 'needs-reason';
@@ -1,10 +1,12 @@
1
- # Document workflow reconciliation — Phase 3b preparation
1
+ # Document workflow reconciliation — Phase 3b S1 runtime
2
2
 
3
- **Status: design and fixture contracts only. Runtime reconciliation is not shipped or activated.**
4
- This document refines the planned [document lifecycle contract](document-lifecycle.md#phase-3b--narrow-integration-contract-planned-not-shipped).
5
- The [owner's E0/E1 decision](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6283237637)
6
- permits design and RED preparation before #341 E0. Even a default-OFF runtime implementation
7
- MUST NOT merge or activate before E0 is reviewed and accepted.
3
+ **Status: S1 runtime ships default OFF; opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`.
4
+ S1 grants no positive document verification credit. S2/S3 are pending; E1 has not started.**
5
+ This document refines the [document lifecycle contract](document-lifecycle.md#phase-3b--narrow-integration-contract-s1-shipped-default-off-positive-reconciliation-not-shipped).
6
+ The [accepted E0](https://github.com/dyphn1/Harness-everything/issues/341#issuecomment-6308604128)
7
+ permits this limited, reversible consumer under the
8
+ [owner's E0/E1 decision](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6283237637).
9
+ Current evidence establishes deterministic mechanism/package behavior, not live-host loading or E1 GO.
8
10
 
9
11
  ## Entry gates and ownership
10
12
 
@@ -112,7 +114,7 @@ capabilities, host requirements and existing binding/tool locators; it does not
112
114
  or load SKILL.md content. Candidates are optional. Swapping or omitting a provider cannot alter
113
115
  expected obligations, authorize a tool, or convert missing evidence to PASS. Existing binding
114
116
  resolution handles a chosen provider; independent valid evidence can satisfy a MUST without it.
115
- An unsupported host reports unavailable capability/reason. H1/H2 runtime proof remains pending.
117
+ An unsupported host reports unavailable capability/reason. S3 provider integration and its H1/H2 runtime proof remain pending.
116
118
 
117
119
  ## S3 — advisory provider suggestions
118
120
 
@@ -227,16 +229,16 @@ live-host loading, E1 GO or #320 promotion. No S1 implementation is a dependency
227
229
 
228
230
  ## One switch and rollback
229
231
 
230
- Planned switch: `HARNESS_DOC_RECONCILIATION_PILOT=1`. Missing, `0`, or any other value is OFF.
231
- No production switch reader or runtime import is part of preparation. Test children explicitly
232
- set or remove the variable; requesting ON during prep still runs existing legacy production code.
232
+ S1 reads `HARNESS_DOC_RECONCILIATION_PILOT=1`; missing, `0`, or any other value is OFF.
233
+ OFF returns before pilot collection or state/report writes. Test children explicitly set or remove
234
+ the variable; ON invokes the S1 runtime's scope assessment and no-credit gate.
233
235
 
234
- After E0, OFF preserves legacy disposition and grants zero pilot verification credit. Legacy
236
+ OFF preserves legacy disposition and grants zero pilot verification credit. Legacy
235
237
  `pass` or `satisfied` is not relabeled as document verification. Rollback removes the opt-in,
236
238
  records that pilot credit is disabled, and preserves session/evidence history. ON→OFF→ON MUST
237
239
  recollect/reconcile rather than trust any persisted pilot or legacy PASS. Withholding pilot
238
- credit never rejects Stop or changes host authorization. Runtime activation still requires E0;
239
- the switch cannot serve as a substitute for that acceptance gate.
240
+ credit never rejects Stop or changes host authorization. Accepted E0 permits the limited S1 opt-in;
241
+ the switch does not authorize expansion or replace E1 GO.
240
242
 
241
243
  ## Preparation harness handoff
242
244
 
@@ -262,12 +264,12 @@ replay cases:
262
264
  | P07 | `P07-fable-staged`, `P07-fable-parallel`, `P07-fable-multi-agent-workspace`, `P07u` | Three Fable strategies and the untyped compatibility control |
263
265
 
264
266
  P08 is the suite-level source/package parity check across every replay and the shared runtime bytes;
265
- it is not a separate `cases.json` record. The 16 cases currently produce 13 `EXPECTED_RED` cases
266
- and three `CONTROL_PASS` cases (`P02d`, `P06`, and `P07u`); all replay legs must agree. These
267
+ it is not a separate `cases.json` record. The 16 prep cases currently produce 13 `CONTROL_PASS`
268
+ cases and three `EXPECTED_RED` cases (`P02e`, `P03`, and `P05`); all replay legs must agree. These
267
269
  statuses qualify the preparation harness only and do not grant document credit.
268
270
 
269
271
  The [pending-red.json registry](../ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json)
270
- records 62 exact source/packaged gaps. Its root records `schemaVersion`, `phase`, `baselineSha`,
272
+ records 10 exact source/packaged S2 gaps (five assertion IDs × two distributions), after 52 S1 entries were retired. Its root records `schemaVersion`, `phase`, `baselineSha`,
271
273
  `runtimeFingerprint`, and `entries`. The runner validates schema version, a 40-hex capture
272
274
  `baselineSha`, a 64-hex `runtimeFingerprint`, and the entry array. Each entry has exactly
273
275
  `caseId`, `assertionId`, `distribution`, `baselineSha`, `actual`, `desired`, `blocker`, and
@@ -323,16 +325,205 @@ identifies its `leg` (`source` or `packaged`), `mode` (`off` or `on`), full asse
323
325
  classification `status`, and `actual`, with `desired`/`registered` values when applicable. Case
324
326
  statuses are `EXPECTED_RED`, `CONTROL_PASS`, `INFRASTRUCTURE`, `NOT_RUN`, or `FAIL`; assertion statuses distinguish
325
327
  passing controls, unexpected passes, qualified gaps, unqualified gaps, and registry mismatches.
326
- `pilotCredit` is always false. `reportOrigin` belongs to the replay's `pilotReportOrigin`
327
- (`runtime` only for an actual runtime report, otherwise `harness` for a synthesized marker);
328
- it is not a field on the preparation report's assertion rows. A harness-synthesized unavailable
328
+ `pilotCredit` is always false. The replay records origin in `pilotReportOrigin`
329
+ (`runtime` only for an actual runtime report, otherwise `harness` for a synthesized marker).
330
+ Preparation assertion rows do not carry `reportOrigin`; the S1 runtime report carries its own origin. A harness-synthesized unavailable
329
331
  marker is never presented as a production report.
332
+ ## S1 — switch, trusted scope and fail-closed no-credit gate
333
+
334
+ **S1 runtime ships default OFF (`HARNESS_DOC_RECONCILIATION_PILOT=1` opts in). It grants no positive document verification credit; S2/S3 are pending and E1 has not started.**
335
+ The [accepted E0](https://github.com/dyphn1/Harness-everything/issues/341#issuecomment-6308604128)
336
+ permits this limited implementation; the [posted execution plan](https://github.com/dyphn1/Harness-everything/issues/325)
337
+ orders Docs → RED → GREEN → independent review → green CI for each slice. S1 is a temporary
338
+ negative-credit slice. S2 supplies trusted producer records, independent review provenance,
339
+ positive reconciliation and freshness; only then can E1 observation begin. S3 adds optional
340
+ provider suggestions. None of these slices authorizes wider #320 integration.
341
+
342
+ ### Switch and scope decision
343
+
344
+ Only the exact string `1` enables `HARNESS_DOC_RECONCILIATION_PILOT`. Unset, `0`, `true`,
345
+ whitespace and every other value are OFF. OFF MUST return before any pilot collection or
346
+ report mutation and preserve ordinary output, status and evidence history. Legacy PASS earns
347
+ zero pilot credit. Each ON assessment recollects current scope, including ON→OFF→ON; persisted
348
+ pilot reports or legacy PASS cannot substitute for collection. No discovery runs a generator,
349
+ verifier or network fetch.
350
+
351
+ On supported non-Fable typed workflows (`stepBindingsEnabled === true`), doc-governed means
352
+ current trusted task/config/baseline/candidate facts establish an applicable document MUST,
353
+ or cannot exclude one. Governed/legacy paths and document roles are evaluated at both old and
354
+ new endpoints, so deletion or rename cannot erase scope. D6.5 facts also include declared derived
355
+ sources, normative content and REQ-citing tests. Use the union of derived obligations; a path
356
+ suffix or an authored step name alone is not a scope assessment. A declared `doc-change` MAY
357
+ request assessment but is neither sufficient nor required for governed applicability. Omitted,
358
+ unknown, incomplete or unavailable scope MUST be treated as governed for withholding credit;
359
+ it cannot establish an empty expected set or opt out. Only a trusted non-document assessment
360
+ retains legacy behavior (P06). Unknown applicability remains unresolved, N/A needs its derivation
361
+ reason, and SHOULD is not a gate.
362
+
363
+ - **Config authority (PR #351 review P1).** Derivation uses the current config, so a candidate config edit could de-scope a baseline-governed path. Before deriving, S1 compares the exact bytes of every config source the resolver can select (`.agents/harness-everything/project-docs.json` and each host `harness-everything/manifest.json`) at the recorded baseline commit and in the candidate, including additions and removals. Any difference fails closed with reason `config-scope-changed`; it never establishes trusted non-document scope. S2 replaces this with a full `configDigest` binding.
364
+ - A code-only diff (changed code with no changed REQ-citing test) yields `requirement-sync` applicability `unknown` (a MUST), so it is withheld under ON. This is the unknown-applicability cost the Docuvia pilot measures in E1; see [#350](https://github.com/dyphn1/Harness-everything/issues/350) for the false-rejection measurement.
365
+
366
+ The following anchors are **source-traced at S1 base `5f9726c`**, not runtime or live-host proof:
367
+
368
+ | Input / boundary | Existing seam and limit | S1 requirement |
369
+ | --- | --- | --- |
370
+ | Config selection | `harness-everything/scripts/project-docs-resolver.js:352` (`resolveDocsConfig`) selects/validates project or host config; active-host, absent and ambiguous branches at `:395–447`. `resolveProjectDocs` at `:607` resolves project paths, not a complete evidence identity | Reuse resolver authority and diagnostics; invalid/ambiguous config, or absent config with governed-scope uncertainty, cannot establish trusted non-doc scope. Parsed config and the bytes selected must agree |
371
+ | Baseline / candidate collection | `harness-everything/scripts/doc-lifecycle.js:1534` (`readGitBaseInventory`) reads Git base blobs/config scope; candidate stored-byte walk at `:1398–1425`; lifecycle baseline consumption at `:1971`. These are lifecycle internals, not an exported workflow scope collector (`:2399`) | Reuse existing stored-byte semantics, but establish current task scope and complete baseline/candidate inventories, including untracked, deletion and rename endpoints; no caller-supplied fixture map is runtime authority |
372
+ | Facts / obligations | `harness-everything/scripts/doc-obligations.js:1375` (`deriveDocFacts`) accepts snapshots/config, not host/VCS collection; `:1065` classifies governed paths; `:1390–1420` evaluates both endpoints; `:1508–1543` (`deriveDocObligations`) handles applicable/unknown capabilities | Reuse these pure APIs unchanged after trusted collection. Input shape validity is not provenance or inventory completeness |
373
+ | Verification-step PASS | `hooks/scripts/workflow-disposition.js:279–296` delegates to `updateStep`; `hooks/scripts/lib/workflow-obligations.js:617–645` validates active step/bindings then writes status | Collect current config and diff scope after ordinary validation, before writing verification PASS |
374
+ | Direct aggregate PASS | `hooks/scripts/workflow-disposition.js:240–258` delegates to `updateObligation`; `hooks/scripts/lib/workflow-obligations.js:513–539` accepts evidence text and writes the aggregate | Assess scope before direct `execute` or `verify` PASS, even after every authored step has passed |
375
+ | Completion | `hooks/scripts/lib/workflow-obligations.js:648–682` reports only current active-step evidence; `hooks/scripts/workflow-stop-gate.js:36–64` checks unresolved items then may persist satisfied | Recollect config and diff scope before every completion decision, including already-satisfied and all-PASS plans |
376
+
377
+ ### S1 pilot-local scope collector
378
+
379
+ The minimal collector ships in S1 at the seams above, enabled only by the opt-in switch.
380
+ Its deterministic mechanism/package coverage does not establish live-host loading or positive credit.
381
+
382
+ | Input / decision | S1 contract |
383
+ | --- | --- |
384
+ | Baseline capture | With the switch ON at workflow `plan`, record the repository top-level and the `git rev-parse HEAD` commit in workflow state; ON `start` captures them if the plan lacked a baseline. OFF returns early and records nothing |
385
+ | Baseline unavailable | Not a Git repository, a Git failure or a missing baseline record means baseline `unavailable`; scope cannot be excluded and is treated as governed, withholding credit. An OFF-planned workflow first assessed ON at a credit boundary therefore withholds, including OFF-seeded legacy PASS→ON |
386
+ | Candidate | Collect the current working tree, including non-ignored untracked files, deletions and renames against the recorded baseline commit. Reuse the existing `doc-lifecycle.js` readers / `readGitBaseInventory` and stored-byte semantics; do not re-implement byte normalization. Incomplete inventory means unknown scope, treated as governed |
387
+ | Config | Use existing `resolveDocsConfig` at the candidate. Invalid or ambiguous selection, or absent config with governed-scope uncertainty, cannot establish trusted non-document scope. S1 does not yet bind `configDigest` or the task identity tuple; freshness/provenance binding belongs to S2 |
388
+ | Decision | Feed collected snapshots/config into unchanged `deriveDocFacts` → `deriveDocObligations`. Doc-governed means any applicable or unknown document MUST. Trusted non-document scope requires complete collection and no applicable or unknown document MUST |
389
+
390
+ Do not use HEAD at assessment as the baseline: a commit during the workflow could erase the
391
+ governed diff and bypass withholding by appearing non-document.
392
+
393
+ After baseline capture at plan/start, scope collection runs only at verification PASS writes,
394
+ direct aggregate PASS writes and completion. It never runs generators, verifiers or network
395
+ operations. Each boundary recollects candidate/config against the recorded baseline; ON→OFF→ON
396
+ cannot replace that baseline with the latest HEAD or trust a persisted PASS.
397
+
398
+ **Harness materialization:** prep replays create a temporary workspace, materialize non-empty
399
+ `input.baseFiles` as a committed Git baseline before `plan`, then materialize
400
+ `input.candidateFiles` as the working tree. This lets P06 establish trusted non-document scope
401
+ and governed cases exercise actual diffs. Fixtures without `baseFiles` remain non-Git, so their
402
+ baseline is unavailable and credit is withheld. Fixture maps alone are not runtime authority.
403
+
404
+ - **Accepted S1 residual (gitignored paths):** ignored, untracked candidate content is outside the non-ignored inventory. S1 grants no positive credit either way; the S2 producer must close this gap or explicitly re-accept it.
405
+
406
+ ### Refusal and completion contract
407
+
408
+ | Attempt with ON + supported governed/unknown scope | Required S1 result |
409
+ | --- | --- |
410
+ | Authored `doc-change` or review PASS | Remains reachable under existing binding/authorization rules; later missing verification does not reject earlier authored work |
411
+ | Verification-step PASS | Exit `2`; verification status remains pending; no positive document credit |
412
+ | Direct aggregate `obligation --obligation-id execute\|verify --disposition pass` | Exit `2`; requested aggregate stays pending; direct text cannot bypass the gate |
413
+ | Stop / completion, including persisted or legacy PASS | Withhold current completion credit; report `step:<verify>:pending` for the verification step even if no step is active; aggregate verify is effectively pending and workflow cannot remain credited as satisfied |
414
+
415
+ **Persistence failure (PR #351 review P2).** The withholding decision is applied in memory first: aggregate
416
+ verify becomes pending, a `satisfied` workflow becomes `running`, and `step:<verify>:pending` is reported.
417
+ Persisting that state is best-effort afterwards. A write failure adds the diagnostic `withhold-persist-failed`
418
+ and a reminder; it never leaves the credit decision unapplied, never lets a persisted `satisfied`/PASS count as
419
+ credit, and never makes Stop exit non-zero. Every later ON assessment recollects regardless of what is on disk.
420
+
421
+ **Unwritable session store (PR #351 follow-up).** S1 cannot durably downgrade a store it cannot write: if both the
422
+ obligations write and Stop's `saveWorkflow()` fail, the files on disk may still say `satisfied` / `verify: pass`.
423
+ S1 therefore guarantees the following **read/credit contract** rather than durable invalidation:
424
+
425
+ | Guarantee | S1 behaviour when every session write fails |
426
+ | --- | --- |
427
+ | Persisted state is never credit under ON | Every ON verification, direct aggregate and Stop assessment recollects scope and re-derives the decision; a stored `satisfied`, `verify: pass` or old pilot report is never read as document credit. `pilotCredit` is always `false` in S1. |
428
+ | The decision is still emitted | Stop still prints the `Unresolved workflow evidence: … step:<verify>:pending` reminder and a `withhold-persist-failed` diagnostic, after the failed `saveWorkflow()` rather than instead of it. |
429
+ | Stop stays non-blocking | Stop exits `0`; no lock, no retry loop. |
430
+ | Not claimed | A durable on-disk downgrade, or that a non-pilot reader (OFF, or another tool) will see `running`. With the switch OFF, stored legacy state is legacy state. |
431
+
432
+ A successful in-memory downgrade is **not** counted as durable invalidation.
433
+
434
+ **Scope of the recovery (PR #351 re-review).** Stop's local persistence recovery and the `withhold-persist-failed`
435
+ diagnostic apply **only** when this Stop run made an actual ON, supported, withheld pilot decision. With the switch
436
+ OFF, and for Fable or persisted untyped workflows, a failing `saveWorkflow()` keeps the pre-S1 path unchanged: the
437
+ error reaches Stop's outer handler, exactly as legacy behaviour, and no pilot diagnostic is emitted.
438
+
439
+ Withholding persisted credit MUST NOT reset authored `doc-change`, review, bindings or evidence
440
+ history merely to manufacture an active step. Compute current unresolved verification independently
441
+ of saved PASS; do not trust old aggregate status. Test OFF-completed→ON and an all-PASS typed plan
442
+ that acquires governed scope. Both `hooks/scripts/workflow-stop-gate.js:12–65` and
443
+ `hooks/scripts/stop-gate.js:14–47` MUST still exit `0`, including collector errors. Reminders never
444
+ reject Stop, authorize a tool or introduce a tool lock. Only disposition PASS attempts return `2`.
445
+ Existing binding/authorization diagnostics precede pilot refusal (P02d).
446
+
447
+ ### Runtime pilot report and unsupported paths
448
+
449
+ The S1 runtime report uses the shape specified in
450
+ [trust-contract.json](../ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json):
451
+ `schemaVersion`, `pilotStatus`, `pilotCredit`, `identity`, `expectedMustIds`, `results`,
452
+ `unsatisfied`, `diagnostics`. Each unsatisfied item contains `obligationId`, `state`, `reasonCode`.
453
+ S1 always reports `pilotCredit: false`; it cannot emit `verified`. Missing producer identity is
454
+ `null`; unknown expected scope stays diagnostic/unresolved, never an inferred empty-set PASS.
455
+ `results` retain checker observation/outcome enums unchanged. Supported withheld scope reports
456
+ `unavailable` when collection/producer support is absent, otherwise `unresolved`; known MUSTs
457
+ remain listed without pretending that producer evidence exists. S1 reason codes are
458
+ `unknown-applicability` for an unknown MUST, `producer-unavailable` for an applicable MUST
459
+ without the S2 producer, `baseline-unavailable`, `config-unavailable`, `scope-unavailable`
460
+ for missing baseline/config or incomplete inventory, and `collector-unavailable` for other
461
+ collector failures. Unsupported paths diagnose `unsupported-workflow`; a complete assessment
462
+ with no applicable/unknown MUST diagnoses `trusted-non-document-scope`.
463
+
464
+ An actual runtime report MUST carry `reportOrigin: runtime`; replay extraction sets
465
+ `pilotReportOrigin: runtime` only for that captured report. The helper's
466
+ `ci/lib/doc-reconciliation-prep-fixtures.js:runtimePilotOf()` reads the emitted report;
467
+ missing reports retain `pilot: null` and origin `harness`, never a synthesized runtime marker.
468
+ OFF does not produce or mutate a pilot report.
469
+
470
+ With ON, loaded `fable-staged`, `fable-parallel`, `fable-multi-agent-workspace` and persisted
471
+ untyped workflows report `unavailable`, with no credit and a reason. Fable stage bindings,
472
+ including a correlated loaded stage binding, cannot become document verification; preserve
473
+ existing stage lifecycle rather than entering generic typed-step reconciliation. A **new invalid
474
+ untyped plan** is different: P07u still rejects plan creation with exit `2` and `pilot === null`.
475
+ The separate persisted-untyped fixture covers loaded records. Unsupported paths add no Stop lock.
476
+
477
+ ### Registry staging and independent RED handoff
478
+
479
+ S1 has **52 entries retired / 10 kept**, each assertion ID counted separately for
480
+ `source` and `packaged`. Strict runtime assertions cover the retired entries; the reviewed prep
481
+ registry retains these five S2 assertion IDs × two distributions:
482
+
483
+ | S1 retire: 26 IDs × 2 | Assertion suffixes under `<case>.on.` |
484
+ | --- | --- |
485
+ | P01 | `verify-exit-refused`, `verify-obligation-pending`, `stop-unsatisfied`, `stop-reports-verification` |
486
+ | P04 | `direct-execute-refused`, `direct-verify-refused`, `verify-obligation-pending` |
487
+ | Each of P02a, P02b, P02c, P02f, P02g | `verify-pass-exit`, `stop-state`, `stop-reports` |
488
+ | P03 | `verify-pass-exit` |
489
+ | Each of the three P07 Fable cases | `pilot-status-unavailable` |
490
+ | **S2 keep: 5 IDs × 2** | **Assertion suffixes under `<case>.on.`** |
491
+ | P02e | `review-pass-exit`, `stop-reports` |
492
+ | P03 | `verify-retry-exit` |
493
+ | P05 | `stop-on-2-withheld`, `stop-on-3-withheld` |
494
+
495
+ - Move retired gaps into strict runtime assertions. Declare intentional ON exit `2` in each
496
+ action's `expectExit`; do not weaken crash, output, deadline or other infrastructure policies
497
+ (`ci/lib/doc-reconciliation-prep-fixtures.js:298–352`). Preserve explicit Stop exit `0` checks.
498
+ - P02e independent review provenance belongs to S2. Do not reject review by matching self-review
499
+ prose or because later verifier evidence is absent. P03 first review remains reachable; its
500
+ first verification is refused, and retry remains RED in S1. S2 changes retry ON `expectExit`
501
+ from `2` to `0` when real evidence can earn credit.
502
+ - P05's initial ON positive verification and `stop-on-1` cannot pass in S1. Preserve a distinct S2
503
+ positive-credit case and its approved desired outcomes; add a separate OFF-seeded legacy PASS→ON
504
+ withholding case for S1. Merely changing exit policy introduces an unregistered initial-state gap.
505
+ - Deliberately review/recapture the changed baseline and registry actual/desired qualification;
506
+ include the newly imported pilot module in the ordered runtime fingerprint. Then exactly the
507
+ targeted entries retire and the remaining `--expect-red` run must qualify on that reviewed S1
508
+ baseline. Deleting registry rows alone proves nothing.
509
+ - Additional strict S1 cases: governed diff without authored `doc-change`, direct aggregate PASS
510
+ after authored completion, correlated Fable stage binding, persisted untyped workflow, legacy
511
+ PASS and ON→OFF→ON recollection. These additions are not part of the 52 existing entries.
512
+ - Preserve P06 with trusted non-doc scope, P07u, P02d ordering, OFF controls and source/package
513
+ parity. Require the relevant deterministic gates plus `npm run verify:all -- --fast`, independent
514
+ review with no open P1/P2, and full green CI including Windows on the PR head.
515
+
516
+ S1 proves only its tested no-credit mechanism. It does not prove positive reconciliation,
517
+ independent producer provenance, live-host loading, H1–H4 effectiveness or E1 GO. Registry empty
518
+ is a later strict-prep gate, not proof of any of those evidence layers.
519
+
330
520
  ## Implementation and acceptance after E0
331
521
 
332
- After accepted E0, implement the minimum trusted producer and optional provider adapter, add
333
- the switch reader and narrow W/D/completion integration, package generated copies, and remove
334
- pending exemptions as strict tests become GREEN. Require every expected applicable MUST and
335
- freshness check, source/package compatibility, negative cases and relevant green CI including
336
- Windows. A green preparation harness establishes only its declared mechanism/data checks.
337
- It does not claim runtime reconciliation, E0/E1 acceptance, independent host provenance or
338
- architectural benefit. E1 observations and H1–H4/paired evaluation remain separate gates.
522
+ S1 ships the default-OFF switch, narrow disposition/completion no-credit integration and runtime
523
+ status reporting. S2's trusted producer, positive reconciliation and freshness remain pending;
524
+ S3's optional provider adapter remains pending. Require every expected applicable MUST and
525
+ freshness check before positive credit, plus source/package compatibility, negative cases and
526
+ relevant green CI including Windows. The S1 suites establish their tested mechanism/package
527
+ contracts; they do not establish independent host provenance, live-host loading or architectural
528
+ benefit. E1 has not started and begins only after a creditable S2; E1 GO and H1–H4/paired evidence
529
+ remain separate gates.
@@ -15,19 +15,19 @@ This table distinguishes merged mechanism code from end-to-end workflow integrat
15
15
  | 2 — roles, IDs, aliases, legacy, supersession, index, D6.9 PRD revisions | **Merged** in #327; do not rebuild this lifecycle logic for Phase 3 |
16
16
  | Step-type prerequisite (#326) | **Complete:** PR #328 merged; Harness-owned `doc-change` is explicitly declared, has no implicit TDD binding, and is not auto-inserted by router/System One |
17
17
  | 3a — document facts, obligations and evidence producer | **Merged** in #334: `harness-everything/scripts/doc-obligations.js` and `doc-evidence.js`. H3/H4 have *partial deterministic mechanism* coverage, not integrated workflow evidence. Known limitations: caller-attested envelope; `configDigest` presence-only; `candidateDigest` restricted to `docs/prd/`; missing independent task-scope binding. Deleted and old rename paths are bound to trusted per-path baseline evidence ([doc-evidence-baseline-binding.md](doc-evidence-baseline-binding.md), #335 fixed by #342), with workflow-disposition reconciliation left out of scope |
18
- | 3b — pilot-local provider suggestions and workflow evidence reconciliation | **Preparation complete; runtime implementation not started and #341-gated.** Provider metadata, adapter/interface design and RED fixtures may proceed in parallel with #341 Phase 0A–0C; **runtime** Evidence Reconciliation must not merge or activate before **#341 E0** is accepted (see the entry-gates bullet in the 3b contract below). Implement only a thin consumer adapter; do not wait for a generic #320 registry/CDG. Includes the minimum trusted producer for baseline/candidate/config/task-scope/reviewer identity, fail-closed negative cases, doc-governed gating on `step`/`obligation` writes at the verification/completion boundary, and freshness re-check at completion. The #335 baseline-bound deletion/rename evidence has shipped (#342); claiming complete integration still requires the reconciliation gate above. Preserve existing required-binding compatibility |
18
+ | 3b — pilot-local provider suggestions and workflow evidence reconciliation | **S1 runtime ships default OFF** (opt in with `HARNESS_DOC_RECONCILIATION_PILOT=1`), with trusted scope assessment and fail-closed withholding; **no positive document verification credit**. See [S1 switch, trusted scope and no-credit contract](doc-workflow-reconciliation.md#s1--switch-trusted-scope-and-fail-closed-no-credit-gate). **S3 pure advisory provider suggestions shipped in 0.37.0 (not wired); S2 trusted producer/positive reconciliation/freshness is pending; E1 has not started.** Accepted #341 E0 permits this limited reversible consumer. Current evidence is deterministic mechanism/package coverage, not live-host loading or E1 GO; preserve existing required-binding compatibility |
19
19
  | 3c — H1–H4 and paired-task evaluation | **Not started.** Mechanism tests alone are insufficient for architecture promotion; compare frozen baseline and treatment on real bounded tasks, success/failure first, then conditional cost and latency |
20
20
  | 1b — shared documentation craft and family metadata | **Splittable.** Shared references and thin skill consumers can proceed independently. A small optional logical-family metadata seam may be piloted; physical skill directory moves remain blocked on #320 identity/path separation |
21
21
  | 4 — derived-view generators and `describes:` drift | **Not started.** Preserve generator authorization, deterministic regenerate-equals and scoped reminder semantics |
22
22
 
23
- ### Phase 3b — narrow integration contract (planned, not shipped)
23
+ ### Phase 3b — narrow integration contract (S1 shipped default OFF; positive reconciliation not shipped)
24
24
 
25
25
  The preparation design, test-only provider/trust contracts, and qualified RED replay are complete:
26
26
  [Document workflow reconciliation](doc-workflow-reconciliation.md),
27
27
  [case corpus](../ci/fixtures/doc-obligations/reconciliation-prep/cases.json),
28
28
  [pending-RED registry](../ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json), and
29
- [preparation suite](../ci/mechanism-325-3b-preparation.test.js). Runtime reconciliation remains
30
- unshipped and unactivated until #341 E0 is reviewed and accepted.
29
+ [preparation suite](../ci/mechanism-325-3b-preparation.test.js). #341 E0 is accepted; S1 ships the
30
+ default-OFF no-credit gate only. Positive reconciliation (S2) remains unshipped.
31
31
 
32
32
  - **Inputs:** explicit active `doc-change` step, D6.5 diff facts, Phase 3a obligations/evidence, available provider *metadata*, and host capabilities.
33
33
  - **Entry and activation gates (#341):** parallel preparatory work — provider metadata, adapter/interface design and RED fixtures — may proceed while #341 Phase 0A–0C runs, but the **runtime** Evidence Reconciliation must not merge or activate until **#341 E0** is reviewed and accepted (initial evidence read/binding/outcome conformance, mutation and read-order compatibility, source/package parity, zero false MUST PASS on applicable adversarial cases, and applicable green CI including Windows). After E0, only the **bounded, opt-in, reversible** documentation consumer is eligible for runtime integration at the existing verification/disposition path (no second router, forced provider, new lock, or weakened authorization, with a documented rollback), and it supplies the observed exposure for **#341 E1** (initial target 2–3 genuine development/review cycles with sufficient case mix, not elapsed time). **#341 E1 GO / REVISE / NO-GO gates expansion beyond the limited pilot, not the pilot's own observation** (missing or unrepresentative exposure is `UNCHECKED`, never an automatic GO). The H1–H4 paired-task architectural gate below stays required for any #320 promotion in addition to E1. Shared evidence-read/binding/decision defects route to #341 (fixture-first correction, regression replay, re-observation); documentation policy and the trusted host/VCS producer remain #325 deliverables here.
@@ -4,7 +4,7 @@ description: "Route software/project work through Harness: classify tier/topolog
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.37.0
7
+ version: 0.38.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -2396,6 +2396,6 @@ function main(argv = process.argv.slice(2)) {
2396
2396
  return analysis.diagnostics.some(item => item.severity === 'error') ? 1 : 0;
2397
2397
  }
2398
2398
 
2399
- module.exports = { parseFrontmatter, parseRequirementBlocks, collectDocs, analyzeDocs, resolveRef, lintDocs, buildIndex, diffRevisions };
2399
+ module.exports = { readGitBaseInventory, scanWorkspaceFiles, parseFrontmatter, parseRequirementBlocks, collectDocs, analyzeDocs, resolveRef, lintDocs, buildIndex, diffRevisions };
2400
2400
 
2401
2401
  if (require.main === module) process.exitCode = main();