harness-everything 0.36.4 → 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 (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/ci/fixtures/doc-obligations/providers-s3/cases.json +535 -0
  5. package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +70 -11
  6. package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +24 -556
  7. package/ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json +1 -1
  8. package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +6842 -0
  9. package/ci/fixtures/doc-obligations/reconciliation-s1/fail-session-writes.js +116 -0
  10. package/ci/lib/doc-reconciliation-prep-fixtures.js +116 -10
  11. package/ci/mechanism-325-3b-preparation.test.js +8 -8
  12. package/ci/mechanism-325-3b-s1-review.test.js +831 -0
  13. package/ci/mechanism-325-3b-s1.test.js +188 -0
  14. package/ci/mechanism-325-3b-s3-providers.test.js +378 -0
  15. package/ci/prep/doc-reconciliation-red.js +2 -1
  16. package/docs/doc-workflow-reconciliation.md +333 -28
  17. package/docs/document-lifecycle.md +4 -4
  18. package/harness-everything/SKILL.md +1 -1
  19. package/harness-everything/scripts/doc-lifecycle.js +1 -1
  20. package/harness-everything/scripts/doc-providers.js +113 -0
  21. package/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
  22. package/hooks/scripts/lib/workflow-obligations.js +44 -0
  23. package/hooks/scripts/workflow-disposition.js +2 -0
  24. package/hooks/scripts/workflow-stop-gate.js +24 -1
  25. package/opencode-plugin/plugin.json +1 -1
  26. package/package.json +1 -1
  27. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  28. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
  29. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +44 -0
  30. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +2 -0
  31. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +24 -1
  32. package/plugins/harness-everything/plugin.json +1 -1
  33. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  34. package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +1 -1
  35. package/plugins/harness-everything/skills/harness-everything/scripts/doc-providers.js +113 -0
  36. package/sbom.cdx.json +7 -7
@@ -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
 
@@ -96,8 +98,11 @@ the existing authorized verification path. Snapshot consistency is not VCS or re
96
98
 
97
99
  ## Advisory provider contract
98
100
 
99
- The [provider data contract](../ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json)
100
- specifies the planned interface:
101
+ **S3 implements the pure suggestion function below; it is not wired into workflow authorization,
102
+ bindings or reconciliation.** This Docs-stage contract precedes RED/GREEN and is not a claim of
103
+ completed runtime implementation. The
104
+ [provider data contract](../ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json)
105
+ specifies its interface:
101
106
 
102
107
  ```text
103
108
  suggestDocProviders({ activeStep, neededCapabilities, availableProviderMetadata, hostCapabilities })
@@ -109,20 +114,131 @@ capabilities, host requirements and existing binding/tool locators; it does not
109
114
  or load SKILL.md content. Candidates are optional. Swapping or omitting a provider cannot alter
110
115
  expected obligations, authorize a tool, or convert missing evidence to PASS. Existing binding
111
116
  resolution handles a chosen provider; independent valid evidence can satisfy a MUST without it.
112
- 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.
118
+
119
+ ## S3 — advisory provider suggestions
120
+
121
+ S3 delivers the pure named export `suggestDocProviders(input)` in
122
+ `harness-everything/scripts/doc-providers.js`, with the installed copy generated by
123
+ `npm run plugin:sync`. This is an independent slice from S1: it imports no S1 hook or pilot code
124
+ and changes no workflow disposition. The
125
+ [approved S3 row](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6308606462),
126
+ [suggestion signature](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6230246088)
127
+ and [H1/H2 boundary](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6230191562)
128
+ are the scope; the provider data contract supplies the schema and examples.
129
+
130
+ ### Purity and validation
131
+
132
+ Identical inputs produce identical output, including ordering and reason strings. The function
133
+ MUST NOT mutate input or retained caller objects, read files/SKILL.md, access the network,
134
+ spawn processes, execute tools/generators, discover host state or load future-step content.
135
+ All decisions use supplied metadata only; importing the module has no such side effects either.
136
+
137
+ Validate the complete input before producing suggestions. Required records are non-null objects,
138
+ not arrays; required strings must be strings with non-whitespace content, without coercion.
139
+ Matching uses exact, case-sensitive values; validation does not normalize identifiers.
140
+
141
+ | Input | Required shape |
142
+ | --- | --- |
143
+ | `input` | Object containing all four required fields below |
144
+ | `activeStep` | Object with nonempty `id` and `stepType`; identifies the caller's current authorized active step, not an instruction to authorize it |
145
+ | `neededCapabilities` | Array of distinct, nonempty capability strings; empty is valid |
146
+ | `availableProviderMetadata` | Array of metadata objects; empty is valid |
147
+ | Each metadata record | Nonempty stable `providerId`; `capabilities` is an array of distinct nonempty strings; `stepTypes` and `requiresHostCapabilities` are arrays of nonempty strings; empty host requirements means none |
148
+ | `locator` in each record | Object with `kind` exactly `binding` or `tool`, and nonempty `id`; a lookup locator, never an executable command |
149
+ | `hostCapabilities` | Required array of distinct, nonempty supported-host capability strings; empty means no supported capabilities are declared |
150
+
151
+ Extra fields confer no capability or authority and are not copied into suggestions. Validate
152
+ metadata structurally without exposing unrelated record values in diagnostics. This slice adds
153
+ no provider registry, skill-content parser or host-capability probe.
154
+
155
+ **Invalid input returns a structured result, not a validation exception.** Use
156
+ `{ suggestions: [], unavailable: [...], diagnostics: [{ code: "invalid-input", path, message }] }`.
157
+ Each diagnostic has nonempty string `path`/`message`; order diagnostics by field path, then
158
+ message. Emit no suggestions from a partly valid input. If `neededCapabilities` is structurally
159
+ valid, retain every needed capability exactly once in `unavailable`, with reason `invalid-input`;
160
+ otherwise leave that array empty and diagnose the malformed needed set. Empty error arrays are
161
+ not evidence that no obligations exist. Normal valid output has only the canonical
162
+ `suggestions` and `unavailable` arrays; diagnostics are the validation-error extension only.
163
+
164
+ This follows the data-result convention of `deriveDocFacts` (`doc-obligations.js:1375`, invalid
165
+ inputs yield `inputKnown: false`) and `deriveDocObligations` (`:1508`, invalid facts retain unknown
166
+ checks), plus the checker's structured diagnostics (`doc-evidence.js:88–127`). It does not copy
167
+ invalid evidence into a positive result or modify any of those APIs.
168
+
169
+ **Host uncertainty decision:** retain strict validation. Missing, null or malformed
170
+ `hostCapabilities` is an invalid input, not an alternate unknown-host encoding. For a valid
171
+ supported-capability array, a required capability absent from the array is unavailable. The
172
+ contract's `unknown` availability enum remains reserved: current inputs have no representation
173
+ of unknown host support, so S3 does not invent one or emit `unknown` from malformed input.
174
+ Any future unknown-host representation must preserve unknown as unknown rather than available.
175
+
176
+ ### Filtering, results and ordering
177
+
178
+ For valid input, apply these rules without loading provider content:
179
+
180
+ 1. A record is eligible only if its `stepTypes` contains `activeStep.stepType` and its
181
+ `capabilities` intersects `neededCapabilities`. Future/unrelated-step records and unrelated
182
+ capabilities do not appear in output; an eligible multi-capability record contributes one
183
+ suggestion for each matched needed capability.
184
+ 2. A suggestion contains exactly `capability`, `providerId`, `availability`, `reason`, with
185
+ nonempty strings and availability from the contract enum. Locator values and other metadata
186
+ are not returned. If all `requiresHostCapabilities` are present in `hostCapabilities`, report
187
+ `available` with reason `active-step capability and host requirements match`.
188
+ 3. If host requirements are unmet, keep the eligible suggestion as `unavailable`, with reason
189
+ `host lacks <missing capabilities>`. Sort missing names and join them with `, `; no missing
190
+ requirement may be treated as supported. Empty requirements always pass this host check.
191
+ 4. For every needed capability with **no available suggestion**, emit exactly one
192
+ `{ capability, reason }` in `unavailable`. With no eligible metadata, use
193
+ `no available provider metadata for the active-step capability`. Otherwise join the distinct
194
+ blocking suggestion reasons in sorted order with `; `. An unavailable candidate remains
195
+ visible even if a different candidate is available; that capability is then absent from the
196
+ top-level `unavailable` list. This list reports capability gaps and never removes a MUST.
197
+ 5. Output is a candidate set with **no ranking or default selection**. For deterministic display,
198
+ sort suggestions by capability, then providerId, then availability and reason; sort
199
+ `unavailable` by capability. Use case-sensitive text comparison, independent of locale.
200
+ Tests compare candidate sets; ordering grants no preference, authorization or binding.
201
+
202
+ The three provider-contract examples are executable acceptance inputs: two active alternatives
203
+ are available while the future outline provider is absent; omitting candidates retains a needed
204
+ capability as unavailable; a missing independent-review host capability keeps both the blocked
205
+ candidate and the top-level gap with reason `host lacks independent-review-events`.
206
+
207
+ ### RED boundary and deferred integration
208
+
209
+ Independent RED covers the contract examples, active-step and capability intersection, multiple
210
+ host requirements, empty arrays, missing/null/wrong-type fields, invalid strings/locators and
211
+ explicit distinct-array violations. Assert complete output shape, stable order/reasons, input
212
+ non-mutation (including frozen inputs), no file/network/process/skill-content activity and
213
+ source/package parity. Unsupported host capabilities produce reasons rather than a silent empty
214
+ list; malformed inputs follow the structured-error contract above. No runtime host support is
215
+ inferred from a provider ID, locator, loaded skill or success-looking free text.
216
+
217
+ S3 is not wired into authorization, binding selection or evidence reconciliation. Obligations
218
+ still come exclusively from unchanged `deriveDocObligations`; swapping or omitting suggestions
219
+ cannot change their identity, strength, applicability, scope or acceptance. The suggestion module
220
+ neither invokes the checker nor grants PASS. Existing binding resolution applies only when a
221
+ caller later chooses a provider through its already-authorized workflow.
222
+
223
+ H1/H2 **workflow integration proof remains S2/close-out**, per the reviewed acceptance boundary:
224
+ provider swap/omission leaves expected obligations identical; independent qualifying alternate
225
+ evidence can satisfy a MUST without the suggestion; missing evidence stays unresolved; capture
226
+ active-step metadata and file-read behavior plus unsupported-host reasons. S3's pure-function
227
+ and package tests establish only that local mechanism, not reconciled-workflow H1/H2 proof,
228
+ live-host loading, E1 GO or #320 promotion. No S1 implementation is a dependency of this slice.
113
229
 
114
230
  ## One switch and rollback
115
231
 
116
- Planned switch: `HARNESS_DOC_RECONCILIATION_PILOT=1`. Missing, `0`, or any other value is OFF.
117
- No production switch reader or runtime import is part of preparation. Test children explicitly
118
- 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.
119
235
 
120
- 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
121
237
  `pass` or `satisfied` is not relabeled as document verification. Rollback removes the opt-in,
122
238
  records that pilot credit is disabled, and preserves session/evidence history. ON→OFF→ON MUST
123
239
  recollect/reconcile rather than trust any persisted pilot or legacy PASS. Withholding pilot
124
- credit never rejects Stop or changes host authorization. Runtime activation still requires E0;
125
- 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.
126
242
 
127
243
  ## Preparation harness handoff
128
244
 
@@ -148,12 +264,12 @@ replay cases:
148
264
  | P07 | `P07-fable-staged`, `P07-fable-parallel`, `P07-fable-multi-agent-workspace`, `P07u` | Three Fable strategies and the untyped compatibility control |
149
265
 
150
266
  P08 is the suite-level source/package parity check across every replay and the shared runtime bytes;
151
- it is not a separate `cases.json` record. The 16 cases currently produce 13 `EXPECTED_RED` cases
152
- 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
153
269
  statuses qualify the preparation harness only and do not grant document credit.
154
270
 
155
271
  The [pending-red.json registry](../ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json)
156
- 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`,
157
273
  `runtimeFingerprint`, and `entries`. The runner validates schema version, a 40-hex capture
158
274
  `baselineSha`, a 64-hex `runtimeFingerprint`, and the entry array. Each entry has exactly
159
275
  `caseId`, `assertionId`, `distribution`, `baselineSha`, `actual`, `desired`, `blocker`, and
@@ -209,16 +325,205 @@ identifies its `leg` (`source` or `packaged`), `mode` (`off` or `on`), full asse
209
325
  classification `status`, and `actual`, with `desired`/`registered` values when applicable. Case
210
326
  statuses are `EXPECTED_RED`, `CONTROL_PASS`, `INFRASTRUCTURE`, `NOT_RUN`, or `FAIL`; assertion statuses distinguish
211
327
  passing controls, unexpected passes, qualified gaps, unqualified gaps, and registry mismatches.
212
- `pilotCredit` is always false. `reportOrigin` belongs to the replay's `pilotReportOrigin`
213
- (`runtime` only for an actual runtime report, otherwise `harness` for a synthesized marker);
214
- 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
215
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
+
216
520
  ## Implementation and acceptance after E0
217
521
 
218
- After accepted E0, implement the minimum trusted producer and optional provider adapter, add
219
- the switch reader and narrow W/D/completion integration, package generated copies, and remove
220
- pending exemptions as strict tests become GREEN. Require every expected applicable MUST and
221
- freshness check, source/package compatibility, negative cases and relevant green CI including
222
- Windows. A green preparation harness establishes only its declared mechanism/data checks.
223
- It does not claim runtime reconciliation, E0/E1 acceptance, independent host provenance or
224
- 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.36.4
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();
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ // Suggestions describe candidates only; they never select a provider or satisfy evidence.
4
+ const AVAILABLE = 'active-step capability and host requirements match';
5
+ const NO_METADATA = 'no available provider metadata for the active-step capability';
6
+ const compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
7
+ const isRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value);
8
+ const isText = value => typeof value === 'string' && value.trim().length > 0;
9
+
10
+ function validateInput(input) {
11
+ const diagnostics = [];
12
+ const diagnose = (path, message) => diagnostics.push({ code: 'invalid-input', path, message });
13
+ const text = (value, path) => {
14
+ if (!isText(value)) diagnose(path, 'Expected a nonempty string.');
15
+ };
16
+ const strings = (value, path, distinct = false) => {
17
+ const before = diagnostics.length;
18
+ if (!Array.isArray(value)) {
19
+ diagnose(path, 'Expected an array of nonempty strings.');
20
+ return false;
21
+ }
22
+ const seen = new Set();
23
+ for (let i = 0; i < value.length; i += 1) {
24
+ text(value[i], `${path}[${i}]`);
25
+ if (distinct && seen.has(value[i])) diagnose(`${path}[${i}]`, 'Expected distinct strings.');
26
+ seen.add(value[i]);
27
+ }
28
+ return diagnostics.length === before;
29
+ };
30
+
31
+ if (!isRecord(input)) {
32
+ diagnose('input', 'Expected an object.');
33
+ return { diagnostics, neededValid: false };
34
+ }
35
+ if (!isRecord(input.activeStep)) {
36
+ diagnose('activeStep', 'Expected an object.');
37
+ } else {
38
+ text(input.activeStep.id, 'activeStep.id');
39
+ text(input.activeStep.stepType, 'activeStep.stepType');
40
+ }
41
+ const neededValid = strings(input.neededCapabilities, 'neededCapabilities', true);
42
+ strings(input.hostCapabilities, 'hostCapabilities', true);
43
+ if (!Array.isArray(input.availableProviderMetadata)) {
44
+ diagnose('availableProviderMetadata', 'Expected an array of metadata objects.');
45
+ } else {
46
+ for (let i = 0; i < input.availableProviderMetadata.length; i += 1) {
47
+ const metadata = input.availableProviderMetadata[i];
48
+ const path = `availableProviderMetadata[${i}]`;
49
+ if (!isRecord(metadata)) {
50
+ diagnose(path, 'Expected a metadata object.');
51
+ continue;
52
+ }
53
+ text(metadata.providerId, `${path}.providerId`);
54
+ strings(metadata.capabilities, `${path}.capabilities`, true);
55
+ strings(metadata.stepTypes, `${path}.stepTypes`);
56
+ strings(metadata.requiresHostCapabilities, `${path}.requiresHostCapabilities`);
57
+ if (!isRecord(metadata.locator)) {
58
+ diagnose(`${path}.locator`, 'Expected a locator object.');
59
+ } else {
60
+ if (!['binding', 'tool'].includes(metadata.locator.kind)) {
61
+ diagnose(`${path}.locator.kind`, 'Expected binding or tool.');
62
+ }
63
+ text(metadata.locator.id, `${path}.locator.id`);
64
+ }
65
+ }
66
+ }
67
+ diagnostics.sort((a, b) => compareText(a.path, b.path) || compareText(a.message, b.message));
68
+ return { diagnostics, neededValid };
69
+ }
70
+
71
+ /** Pure advisory projection of caller-supplied active-step metadata. */
72
+ function suggestDocProviders(input) {
73
+ const { diagnostics, neededValid } = validateInput(input);
74
+ if (diagnostics.length) {
75
+ return {
76
+ suggestions: [],
77
+ unavailable: neededValid
78
+ ? [...input.neededCapabilities].sort(compareText).map(capability => ({ capability, reason: 'invalid-input' }))
79
+ : [],
80
+ diagnostics
81
+ };
82
+ }
83
+
84
+ const needed = new Set(input.neededCapabilities);
85
+ const host = new Set(input.hostCapabilities);
86
+ const suggestions = [];
87
+ for (const metadata of input.availableProviderMetadata) {
88
+ if (!metadata.stepTypes.includes(input.activeStep.stepType)) continue;
89
+ const missing = metadata.requiresHostCapabilities.filter(capability => !host.has(capability)).sort(compareText);
90
+ const availability = missing.length ? 'unavailable' : 'available';
91
+ const reason = missing.length ? `host lacks ${missing.join(', ')}` : AVAILABLE;
92
+ for (const capability of metadata.capabilities) {
93
+ if (needed.has(capability)) {
94
+ suggestions.push({ capability, providerId: metadata.providerId, availability, reason });
95
+ }
96
+ }
97
+ }
98
+ suggestions.sort((a, b) => compareText(a.capability, b.capability)
99
+ || compareText(a.providerId, b.providerId)
100
+ || compareText(a.availability, b.availability)
101
+ || compareText(a.reason, b.reason));
102
+
103
+ const unavailable = [];
104
+ for (const capability of [...needed].sort(compareText)) {
105
+ const candidates = suggestions.filter(suggestion => suggestion.capability === capability);
106
+ if (candidates.some(candidate => candidate.availability === 'available')) continue;
107
+ const reasons = [...new Set(candidates.map(candidate => candidate.reason))].sort(compareText);
108
+ unavailable.push({ capability, reason: reasons.length ? reasons.join('; ') : NO_METADATA });
109
+ }
110
+ return { suggestions, unavailable };
111
+ }
112
+
113
+ module.exports = { suggestDocProviders };