harness-everything 0.40.0 → 0.41.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/.agents/harness-everything/project-docs.json +9 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +43 -0
- package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/2-injected-architecture.md +229 -0
- package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stdout.json +33 -0
- package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stdout.json +20 -0
- package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stdout.json +33 -0
- package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/README.md +54 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-injected-document.md +328 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stdout.json +127 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stdout.json +115 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stdout.json +127 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stderr.txt +0 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stdout.json +15 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.input.md +8 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.stdout.json +16 -0
- package/benchmarks/results/f1-doc-path-baseline/before-fix/README.md +34 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/after.json +248 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/before.json +254 -0
- package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/probe.js +26 -0
- package/benchmarks/results/f2-doc-craft-baseline/README.md +32 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1-report.md +84 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1-task.md +21 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f1.diff +19 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2-report.md +110 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2-task.md +21 -0
- package/benchmarks/results/f2-doc-craft-baseline/arm-f2.diff +19 -0
- package/benchmarks/results/f3-derived-views-baseline/README.md +34 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2-report.md +43 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2-task.md +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f2.diff +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3-report.md +57 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3-task.md +21 -0
- package/benchmarks/results/f3-derived-views-baseline/arm-f3.diff +21 -0
- package/benchmarks/results/live-host/claude-325-3b-s2b/README.md +99 -0
- package/benchmarks/results/live-host/claude-325-3b-s2b/capture.sanitized.jsonl +26 -0
- package/ci/archive/325-prep/README.md +71 -0
- package/ci/{prep → archive/325-prep}/doc-reconciliation-red.js +9 -7
- package/ci/archive/325-prep/f4-expected-red.json +34 -0
- package/ci/archive/325-prep/historical-qualification.test.js +143 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +17 -4
- package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +8 -56
- package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +2 -2
- package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +17 -5
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/harness.js +415 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/off-handoff-golden.json +16 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-failure.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success-minimal.json +9 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success.json +13 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-start.json +7 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-freetext-reject.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-reject.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop.json +11 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-task-result.json +19 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-capture-only.json +99 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-legacy.json +67 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-concurrent-writers.json +62 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-lookalikes-runtime-mutations.json +67 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-malformed.json +85 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-normal-session.json +74 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-runtime-drift-later-npm-test.json +58 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-valid-direct-quoted-review.json +85 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/f4/cases.json +2267 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/cases.json +1110 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/legacy-off-golden.json +674 -0
- package/ci/lib/doc-reconciliation-file-spy.js +23 -0
- package/ci/lib/doc-reconciliation-prep-fixtures.js +136 -10
- package/ci/lib/doc-reconciliation-qualification-preload.js +37 -0
- package/ci/lib/doc-reconciliation-replay.js +104 -0
- package/ci/lib/doc-reconciliation-staged-tree.js +102 -0
- package/ci/mechanism-2p-opencode-v2-entrypoint.test.js +179 -0
- package/ci/mechanism-325-3b-preparation.test.js +27 -101
- package/ci/mechanism-325-3b-s2b-capture.test.js +561 -0
- package/ci/mechanism-325-3b-s2b-wiring.test.js +668 -0
- package/ci/mechanism-325-f1-doc-check.test.js +674 -0
- package/ci/mechanism-325-f2-doc-craft.test.js +597 -0
- package/ci/mechanism-325-f3-derived-views.test.js +798 -0
- package/ci/mechanism-325-f4-invariants.test.js +606 -0
- package/ci/mechanism-325-f4-projections.test.js +382 -0
- package/ci/mechanism-325-f4-qualification.test.js +228 -0
- package/ci/mechanism-325-f4-trust-boundary.test.js +880 -0
- package/ci/mutation-check.js +26 -3
- package/docs/architecture.md +5 -0
- package/docs/doc-workflow-reconciliation.md +331 -4
- package/docs/document-lifecycle.md +11 -0
- package/docs/governed-index.md +20 -0
- package/docs/platform-capabilities.md +22 -2
- package/grill-with-docs/SKILL.md +1 -1
- package/grill-with-docs/references/session-playbook.md +4 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/references/doc-check.md +186 -0
- package/harness-everything/references/documentation/_shared/maintenance.md +41 -0
- package/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
- package/harness-everything/references/triage-and-tiers.md +4 -0
- package/harness-everything/scripts/doc-check.js +442 -0
- package/harness-everything/scripts/doc-lifecycle.js +3 -0
- package/hooks/hooks.json +46 -0
- package/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
- package/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
- package/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
- package/hooks/scripts/lib/workflow-obligations.js +7 -0
- package/hooks/scripts/state-persist.js +78 -2
- package/hooks/scripts/workflow-stop-gate.js +7 -2
- package/opencode-plugin/README.md +15 -0
- package/opencode-plugin/index.mjs +77 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +7 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +78 -2
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +7 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +1 -1
- package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +4 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/references/doc-check.md +186 -0
- package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/maintenance.md +41 -0
- package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +4 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +442 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +3 -0
- package/plugins/harness-everything/skills/repo-docs/SKILL.md +1 -1
- package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +4 -0
- package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-spec/references/process.md +4 -0
- package/plugins/harness-everything/skills/to-tickets/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +4 -0
- package/repo-docs/SKILL.md +1 -1
- package/repo-docs/references/process-guide.md +4 -0
- package/sbom.cdx.json +7 -7
- package/to-spec/SKILL.md +1 -1
- package/to-spec/references/process.md +4 -0
- package/to-tickets/SKILL.md +1 -1
- package/to-tickets/references/publishing-and-templates.md +4 -0
|
@@ -111,10 +111,157 @@ function collect(context) {
|
|
|
111
111
|
return deriveDocObligations(facts).filter(item => item.strength === 'MUST' && item.applicability !== 'N/A');
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// S2b credit chain (F4): produce the snapshot, accept the origin-provider events, and check them against the expected
|
|
115
|
+
// MUSTs. Credit only when the core marks the evidence eligible. Production has no qualification, so it stays withheld.
|
|
116
|
+
function creditChain(context, set, dependencies = {}) {
|
|
117
|
+
if (require('./doc-reconciliation-provider').readCaptureView(context.sessionDir).tampered) {
|
|
118
|
+
return { eligible: false, diagnostics: [{ code: 'journal-tampered', message: 'the capture journal no longer matches its immutable event records' }] };
|
|
119
|
+
}
|
|
120
|
+
const { produceDocSnapshot } = require('./doc-reconciliation-producer');
|
|
121
|
+
const { acceptDocEvents } = require('./doc-reconciliation-events');
|
|
122
|
+
const { checkAcceptedDocEvidence } = require('./doc-reconciliation-core');
|
|
123
|
+
const baseline = context.workflow.docReconciliationBaseline;
|
|
124
|
+
const requirements = Array.isArray(set?.requirements) ? set.requirements : [];
|
|
125
|
+
const taskId = `task-${requirements.map(item => item.id).join('+') || 'none'}`;
|
|
126
|
+
const sessionId = context.sessionId;
|
|
127
|
+
const { createClaudeOriginProvider, journalExecutors } = require('./doc-reconciliation-provider');
|
|
128
|
+
const captures = readCaptures(context);
|
|
129
|
+
const originProvider = dependencies.originProvider || createClaudeOriginProvider({
|
|
130
|
+
hostId: 'claude-code',
|
|
131
|
+
sessionId,
|
|
132
|
+
workspaceRoot: context.root,
|
|
133
|
+
workflowId: context.workflow.workflowId,
|
|
134
|
+
workflowRevision: context.workflow.revision,
|
|
135
|
+
taskId,
|
|
136
|
+
principals: { author: sessionId, executors: journalExecutors(captures, sessionId) },
|
|
137
|
+
baseline,
|
|
138
|
+
captures,
|
|
139
|
+
});
|
|
140
|
+
const produced = produceDocSnapshot({
|
|
141
|
+
workspaceRoot: context.root,
|
|
142
|
+
baseline,
|
|
143
|
+
workflow: { workflowId: context.workflow.workflowId, sessionId, workflowRevision: context.workflow.revision },
|
|
144
|
+
task: { taskId, requirements },
|
|
145
|
+
scope: { prdRoot: 'docs/prd/' },
|
|
146
|
+
activeHost: 'claude-code',
|
|
147
|
+
prdRoot: 'docs/prd/',
|
|
148
|
+
}, { originProvider });
|
|
149
|
+
if (produced.state !== 'ready' || !produced.snapshot) return { eligible: false, diagnostics: produced.diagnostics, unsatisfied: [] };
|
|
150
|
+
const eventRefs = captureReferences(context);
|
|
151
|
+
const accepted = acceptDocEvents({ snapshot: produced.snapshot, eventRefs }, { originProvider });
|
|
152
|
+
const checked = checkAcceptedDocEvidence({ snapshot: produced.snapshot, accepted, mode: 'verification' });
|
|
153
|
+
// Credit covers only the expected MUSTs that were both accepted and satisfied (passing, observed). N/A never counts.
|
|
154
|
+
const passing = new Set((checked.results || []).filter(r => r.observation === 'observed' && r.outcome === 'pass').map(r => r.obligationId));
|
|
155
|
+
const expected = new Set(checked.checkedMustIds || []);
|
|
156
|
+
const satisfied = produced.snapshot.obligations.filter(o => o.strength === 'MUST' && o.applicability !== 'N/A' && expected.has(o.id) && passing.has(o.id));
|
|
157
|
+
return {
|
|
158
|
+
eligible: checked.eligible === true,
|
|
159
|
+
capabilities: checked.eligible === true ? [...new Set(satisfied.map(o => o.capability))] : [],
|
|
160
|
+
satisfiedObligationIds: satisfied.map(o => o.id),
|
|
161
|
+
unsatisfied: checked.unsatisfied || [],
|
|
162
|
+
acceptedRefs: Object.keys(accepted.acceptedIndex || {}),
|
|
163
|
+
diagnostics: [...(accepted.diagnostics || []), ...(checked.diagnostics || [])],
|
|
164
|
+
rejected: accepted.rejected || [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Report diagnostics are objects with the obligation they concern (F12); a global fault has obligationId null.
|
|
169
|
+
function diagnosticOf(item) {
|
|
170
|
+
if (item && typeof item === 'object') return { ...item, code: item.code || 'unknown', obligationId: item.obligationId ?? null };
|
|
171
|
+
return { code: String(item), obligationId: null };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Review-mode refusal (F4 I2a): with the pilot ON in a supported (non-Fable) workflow, a review step PASS needs an
|
|
175
|
+
// independent quality-review record. The provider's own independence rules decide it: a host agent_id that differs from
|
|
176
|
+
// the author session, an explicit approve verdict, a non-empty summary and the bound final-artifact digest.
|
|
177
|
+
function reviewRequired(set) {
|
|
178
|
+
return enabled() && set?.stepBindingsEnabled === true && !String(set?.strategy || '').startsWith('fable-');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The review step is independent of credit qualification: the step records host-observed independence, while document
|
|
182
|
+
// credit itself still needs the validated qualification (assess).
|
|
183
|
+
function independentReviewAccepted(context) {
|
|
184
|
+
const { reviewRecordIndependent, journalExecutors } = require('./doc-reconciliation-provider');
|
|
185
|
+
const view = require('./doc-reconciliation-provider').readCaptureView(context.sessionDir);
|
|
186
|
+
if (view.tampered) return false;
|
|
187
|
+
const captures = view.records;
|
|
188
|
+
const principals = { author: context.sessionId, executors: journalExecutors(captures, context.sessionId) };
|
|
189
|
+
const pin = context.workflow.docReconciliationBaseline;
|
|
190
|
+
return captures.some(item => recordOf(item) && item.session_id === context.sessionId
|
|
191
|
+
&& recordOf(item.observedIdentity) && item.observedIdentity.workflowId === context.workflow.workflowId
|
|
192
|
+
&& item.observedIdentity.workflowRevision === context.workflow.revision
|
|
193
|
+
&& (!pin || (recordOf(item.observedIdentity.baselinePin) && item.observedIdentity.baselinePin.commit === pin.commit))
|
|
194
|
+
&& reviewRecordIndependent(item, captures, principals));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function recordOf(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
|
|
198
|
+
|
|
199
|
+
// The session capture records are the only host-event source. A missing journal means no events; a journal that no longer
|
|
200
|
+
// equals its immutable event files is tampered and yields no events, so the chain withholds.
|
|
201
|
+
function readCaptures(context) {
|
|
202
|
+
return require('./doc-reconciliation-provider').readCaptureView(context.sessionDir).records;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Only explicit PostToolUse doc-check candidates are offered; ordinary Edit or Agent results are not (R3-01).
|
|
206
|
+
const docCheckCandidate = item => item.event === 'PostToolUse'
|
|
207
|
+
&& require('./doc-reconciliation-provider').resolveDocCheckInvocation(item.command, item.cwd).kind !== 'not-candidate';
|
|
208
|
+
|
|
209
|
+
function captureReferences(context) {
|
|
210
|
+
const captures = readCaptures(context);
|
|
211
|
+
const candidates = captures
|
|
212
|
+
.filter(item => item && typeof item.ref === 'string' && (item.event === 'SubagentStop' || docCheckCandidate(item)))
|
|
213
|
+
.filter(item => item.event !== 'SubagentStop');
|
|
214
|
+
const reviews = captures.filter(item => item && typeof item.ref === 'string' && item.event === 'SubagentStop');
|
|
215
|
+
const references = [];
|
|
216
|
+
for (const [index, capability] of ['doc-lint', 'link-check'].entries()) {
|
|
217
|
+
const ordered = index ? candidates.slice(1).concat(candidates.slice(0, 1)) : candidates;
|
|
218
|
+
references.push(...ordered.map(item => `${item.ref}#${capability}`));
|
|
219
|
+
}
|
|
220
|
+
// The adapter accepts one event per capability; rotating candidate order lets separate valid runs fill each capability.
|
|
221
|
+
return [...references, ...reviews.map(item => `${item.ref}#quality-review`)];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// R3-02: verification recorded before an installed-runtime change is withdrawn once the live doc-check leaves the pin.
|
|
225
|
+
// Capture time sees the pristine runtime, so only assessment can catch a later change. Scoped to sessions that ran a doc-check.
|
|
226
|
+
function withdrawChangedRuntimeVerification(context) {
|
|
227
|
+
try {
|
|
228
|
+
const provider = require('./doc-reconciliation-provider');
|
|
229
|
+
const installed = provider.installedDocCheck();
|
|
230
|
+
if (!installed || provider.fileDigest(installed) === provider.SHIPPED_DOC_CHECK_DIGEST) return;
|
|
231
|
+
if (!readCaptures(context).some(item => {
|
|
232
|
+
if (!item || item.event !== 'PostToolUse') return false;
|
|
233
|
+
const invocation = provider.resolveDocCheckInvocation(item.command, item.cwd);
|
|
234
|
+
return invocation.kind === 'accepted' || invocation.reason === 'implementation-pin-mismatch';
|
|
235
|
+
})) return;
|
|
236
|
+
const file = path.join(context.sessionDir, 'handoff-state.json');
|
|
237
|
+
const handoff = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
238
|
+
const genericAt = handoff.lastGenericVerifyAt;
|
|
239
|
+
const hasGeneric = typeof genericAt === 'number' && Number.isFinite(genericAt);
|
|
240
|
+
const hasDoc = typeof handoff.lastDocVerifyAt === 'number' && Number.isFinite(handoff.lastDocVerifyAt);
|
|
241
|
+
if (!hasGeneric && !hasDoc && handoff.lastVerifyAt === undefined) return;
|
|
242
|
+
delete handoff.lastDocVerifyAt;
|
|
243
|
+
delete handoff.lastDocVerifyExitCode;
|
|
244
|
+
if (hasGeneric) {
|
|
245
|
+
handoff.lastVerifyAt = genericAt;
|
|
246
|
+
if (Object.prototype.hasOwnProperty.call(handoff, 'lastGenericVerifyExitCode')) {
|
|
247
|
+
handoff.lastVerifyExitCode = handoff.lastGenericVerifyExitCode;
|
|
248
|
+
} else {
|
|
249
|
+
delete handoff.lastVerifyExitCode;
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
// Older aggregate-only state has unknown ownership; do not preserve it as unaffected evidence.
|
|
253
|
+
delete handoff.lastVerifyAt;
|
|
254
|
+
delete handoff.lastVerifyExitCode;
|
|
255
|
+
}
|
|
256
|
+
atomicWriteJson(file, handoff);
|
|
257
|
+
} catch { /* fail-open: no handoff state yet, or nothing to withdraw */ }
|
|
258
|
+
}
|
|
259
|
+
|
|
114
260
|
// Assessment is deliberately negative-credit only in S1. Unsupported topologies never enter
|
|
115
261
|
// generic step reconciliation, and failures (including report persistence) never escape to Stop.
|
|
116
|
-
function assess(context, set) {
|
|
262
|
+
function assess(context, set, dependencies = {}) {
|
|
117
263
|
if (!enabled()) return { withhold: false };
|
|
264
|
+
withdrawChangedRuntimeVerification(context);
|
|
118
265
|
const report = {
|
|
119
266
|
schemaVersion: 1, reportOrigin: 'runtime', pilotStatus: 'unavailable', pilotCredit: false,
|
|
120
267
|
identity: null, expectedMustIds: [], results: [], unsatisfied: [], diagnostics: [],
|
|
@@ -133,6 +280,27 @@ function assess(context, set) {
|
|
|
133
280
|
reasonCode: item.applicability === 'unknown' ? 'unknown-applicability' : 'producer-unavailable',
|
|
134
281
|
}));
|
|
135
282
|
if (!withhold) report.diagnostics.push('trusted-non-document-scope');
|
|
283
|
+
if (withhold) {
|
|
284
|
+
let chain;
|
|
285
|
+
try { chain = creditChain(context, set, dependencies); }
|
|
286
|
+
catch (error) { chain = { eligible: false, diagnostics: [{ code: 'collector-unavailable', message: String(error.message) }] }; }
|
|
287
|
+
// The unsatisfied list is rewritten from the chain: a MUST that the chain did not satisfy stays listed (F3).
|
|
288
|
+
const satisfied = new Set(chain.satisfiedObligationIds || []);
|
|
289
|
+
report.unsatisfied = [
|
|
290
|
+
...musts.filter(item => !satisfied.has(item.id)).map(item => ({
|
|
291
|
+
obligationId: item.id, state: item.applicability === 'unknown' ? 'unknown' : 'pending',
|
|
292
|
+
reasonCode: item.applicability === 'unknown' ? 'unknown-applicability' : 'producer-unavailable',
|
|
293
|
+
})),
|
|
294
|
+
...(chain.unsatisfied || []),
|
|
295
|
+
];
|
|
296
|
+
report.creditedCapabilities = chain.eligible === true ? chain.capabilities : [];
|
|
297
|
+
report.acceptedRefs = chain.acceptedRefs || [];
|
|
298
|
+
report.diagnostics.push(...(chain.diagnostics || []));
|
|
299
|
+
// Credit needs an empty unsatisfied list across both sources and at least one satisfied capability.
|
|
300
|
+
withhold = !(chain.eligible === true && report.unsatisfied.length === 0 && report.creditedCapabilities.length > 0);
|
|
301
|
+
report.pilotCredit = !withhold;
|
|
302
|
+
report.pilotStatus = report.pilotCredit ? 'credited' : 'unresolved';
|
|
303
|
+
}
|
|
136
304
|
} catch (error) {
|
|
137
305
|
withhold = true;
|
|
138
306
|
const reason = ['baseline-unavailable', 'config-unavailable', 'scope-unavailable', 'config-scope-changed'].includes(error.message)
|
|
@@ -141,6 +309,7 @@ function assess(context, set) {
|
|
|
141
309
|
report.unsatisfied.push({ obligationId: 'doc-change:scope', state: 'unavailable', reasonCode: reason });
|
|
142
310
|
}
|
|
143
311
|
}
|
|
312
|
+
report.diagnostics = report.diagnostics.map(diagnosticOf);
|
|
144
313
|
try { atomicWriteJson(path.join(context.sessionDir, 'doc-reconciliation-pilot.json'), report); }
|
|
145
314
|
catch {
|
|
146
315
|
if (supported) withhold = true;
|
|
@@ -149,4 +318,4 @@ function assess(context, set) {
|
|
|
149
318
|
return { withhold, report };
|
|
150
319
|
}
|
|
151
320
|
|
|
152
|
-
module.exports = { enabled, captureBaseline, assess };
|
|
321
|
+
module.exports = { enabled, captureBaseline, assess, reviewRequired, independentReviewAccepted };
|