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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/ci/fixtures/doc-obligations/providers-s3/cases.json +535 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +70 -11
- package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +24 -556
- package/ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json +1 -1
- package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +6842 -0
- package/ci/fixtures/doc-obligations/reconciliation-s1/fail-session-writes.js +116 -0
- package/ci/lib/doc-reconciliation-prep-fixtures.js +116 -10
- package/ci/mechanism-325-3b-preparation.test.js +8 -8
- package/ci/mechanism-325-3b-s1-review.test.js +831 -0
- package/ci/mechanism-325-3b-s1.test.js +188 -0
- package/ci/mechanism-325-3b-s3-providers.test.js +378 -0
- package/ci/prep/doc-reconciliation-red.js +2 -1
- package/docs/doc-workflow-reconciliation.md +333 -28
- package/docs/document-lifecycle.md +4 -4
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/harness-everything/scripts/doc-providers.js +113 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
- package/hooks/scripts/lib/workflow-obligations.js +44 -0
- package/hooks/scripts/workflow-disposition.js +2 -0
- package/hooks/scripts/workflow-stop-gate.js +24 -1
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +152 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +44 -0
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +2 -0
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +24 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-providers.js +113 -0
- package/sbom.cdx.json +7 -7
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# Document workflow reconciliation — Phase 3b
|
|
1
|
+
# Document workflow reconciliation — Phase 3b S1 runtime
|
|
2
2
|
|
|
3
|
-
**Status:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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.
|
|
125
|
-
the switch
|
|
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 `
|
|
152
|
-
and three `
|
|
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
|
|
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.
|
|
213
|
-
(`runtime` only for an actual runtime report, otherwise `harness` for a synthesized marker)
|
|
214
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
freshness check, source/package compatibility, negative cases and
|
|
222
|
-
Windows.
|
|
223
|
-
|
|
224
|
-
|
|
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 | **
|
|
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 (
|
|
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).
|
|
30
|
-
|
|
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.
|
|
@@ -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 };
|