mustflow 1.31.0 → 2.16.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/README.md +23 -9
- package/dist/cli/commands/classify.js +61 -6
- package/dist/cli/commands/contract-lint.js +13 -4
- package/dist/cli/commands/dashboard.js +77 -2
- package/dist/cli/commands/explain-verify.js +11 -1
- package/dist/cli/commands/index.js +14 -0
- package/dist/cli/commands/run.js +4 -1
- package/dist/cli/commands/verify.js +986 -43
- package/dist/cli/i18n/en.js +61 -10
- package/dist/cli/i18n/es.js +61 -10
- package/dist/cli/i18n/fr.js +61 -10
- package/dist/cli/i18n/hi.js +61 -10
- package/dist/cli/i18n/ko.js +61 -10
- package/dist/cli/i18n/zh.js +61 -10
- package/dist/cli/lib/dashboard-export.js +62 -12
- package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
- package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
- package/dist/cli/lib/dashboard-html/styles.js +572 -0
- package/dist/cli/lib/dashboard-html/template.js +134 -0
- package/dist/cli/lib/dashboard-html/types.js +1 -0
- package/dist/cli/lib/dashboard-html.js +1 -1907
- package/dist/cli/lib/dashboard-locale.js +37 -0
- package/dist/cli/lib/local-index/constants.js +48 -0
- package/dist/cli/lib/local-index/index.js +2951 -0
- package/dist/cli/lib/local-index/sql.js +15 -0
- package/dist/cli/lib/local-index/types.js +1 -0
- package/dist/cli/lib/local-index.js +1 -1911
- package/dist/cli/lib/run-plan.js +76 -1
- package/dist/cli/lib/templates.js +18 -1
- package/dist/cli/lib/validation/command-intents.js +11 -0
- package/dist/cli/lib/validation/constants.js +238 -0
- package/dist/cli/lib/validation/index.js +1384 -0
- package/dist/cli/lib/validation/primitives.js +198 -0
- package/dist/cli/lib/validation/test-selection.js +95 -0
- package/dist/cli/lib/validation/types.js +1 -0
- package/dist/cli/lib/validation.js +1 -1770
- package/dist/core/check-issues.js +6 -0
- package/dist/core/completion-verdict.js +341 -0
- package/dist/core/contract-lint.js +221 -6
- package/dist/core/external-evidence.js +9 -0
- package/dist/core/public-json-contracts.js +21 -0
- package/dist/core/repeated-failure.js +179 -0
- package/dist/core/repro-evidence.js +134 -0
- package/dist/core/scope-risk.js +64 -0
- package/dist/core/skill-route-alignment.js +20 -0
- package/dist/core/source-anchor-status.js +4 -1
- package/dist/core/test-selection.js +3 -0
- package/dist/core/validation-ratchet.js +196 -0
- package/dist/core/verification-evidence.js +249 -0
- package/examples/README.md +12 -4
- package/package.json +3 -3
- package/schemas/README.md +13 -3
- package/schemas/change-verification-report.schema.json +16 -2
- package/schemas/commands.schema.json +4 -0
- package/schemas/contract-lint-report.schema.json +29 -0
- package/schemas/dashboard-export.schema.json +310 -0
- package/schemas/explain-report.schema.json +173 -1
- package/schemas/latest-run-pointer.schema.json +601 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/test-selection.schema.json +81 -0
- package/schemas/verify-report.schema.json +578 -1
- package/schemas/verify-run-manifest.schema.json +627 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
- package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
- package/templates/default/manifest.toml +29 -2
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { createClassifyOutput } from './classify.js';
|
|
4
5
|
import { runRun } from './run.js';
|
|
5
6
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
7
|
+
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
|
+
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
9
|
+
import { createRepeatedFailureRisks, createVerificationFailureFingerprint, updateRepeatedFailureState, } from '../../core/repeated-failure.js';
|
|
10
|
+
import { countReproEvidenceVerdictEffects, createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
11
|
+
import { createVerifyEvidenceModel } from '../../core/verification-evidence.js';
|
|
12
|
+
import { createScopeDiffRisks } from '../../core/scope-risk.js';
|
|
13
|
+
import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } from '../../core/validation-ratchet.js';
|
|
6
14
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
7
15
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
8
16
|
import { t } from '../lib/i18n.js';
|
|
9
|
-
import { readLocalCommandEffectGraph, readLocalPathSurfaces, } from '../lib/local-index.js';
|
|
17
|
+
import { readLocalCommandEffectGraph, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
10
18
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
11
19
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
12
20
|
const VERIFY_RUN_DIR = path.join('.mustflow', 'state', 'runs', 'verify-latest');
|
|
21
|
+
const VERIFY_MANIFEST_PATH = path.join(VERIFY_RUN_DIR, 'manifest.json');
|
|
13
22
|
const LATEST_RUN_RECEIPT_PATH = path.join('.mustflow', 'state', 'runs', 'latest.json');
|
|
14
23
|
function createBufferedOutput() {
|
|
15
24
|
const stdout = [];
|
|
@@ -33,13 +42,16 @@ function createBufferedOutput() {
|
|
|
33
42
|
}
|
|
34
43
|
export function getVerifyHelp(lang = 'en') {
|
|
35
44
|
return renderHelp({
|
|
36
|
-
usage: 'mf verify --reason <event> [options] | mf verify --from-
|
|
45
|
+
usage: 'mf verify --reason <event> [options] | mf verify --from-classification <path> [options] | mf verify --changed [options]',
|
|
37
46
|
summary: t(lang, 'verify.help.summary'),
|
|
38
47
|
options: [
|
|
39
48
|
{ label: '--reason <event>', description: t(lang, 'verify.help.option.reason') },
|
|
49
|
+
{ label: '--from-classification <path>', description: t(lang, 'verify.help.option.fromClassification') },
|
|
40
50
|
{ label: '--from-plan <path>', description: t(lang, 'verify.help.option.fromPlan') },
|
|
41
51
|
{ label: '--changed', description: t(lang, 'verify.help.option.changed') },
|
|
42
52
|
{ label: '--write-plan <path>', description: t(lang, 'verify.help.option.writePlan') },
|
|
53
|
+
{ label: '--repro-evidence <path>', description: t(lang, 'verify.help.option.reproEvidence') },
|
|
54
|
+
{ label: '--external-evidence <path>', description: t(lang, 'verify.help.option.externalEvidence') },
|
|
43
55
|
{ label: '--plan-only', description: t(lang, 'verify.help.option.planOnly') },
|
|
44
56
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
45
57
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
@@ -48,9 +60,9 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
48
60
|
'mf verify --reason code_change',
|
|
49
61
|
'mf verify --reason docs_change --json',
|
|
50
62
|
'mf verify --reason docs_change --plan-only --json',
|
|
51
|
-
'mf verify --from-
|
|
63
|
+
'mf verify --from-classification .mustflow/state/change-classification.json --json',
|
|
64
|
+
'mf verify --reason bug_fix --repro-evidence repro-evidence.json --json',
|
|
52
65
|
'mf verify --changed --plan-only --json',
|
|
53
|
-
'mf verify --changed --write-plan .mustflow/state/change-plan.json --json',
|
|
54
66
|
'mf verify --reason mustflow_docs_change',
|
|
55
67
|
],
|
|
56
68
|
exitCodes: [
|
|
@@ -61,8 +73,11 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
61
73
|
}
|
|
62
74
|
function parseVerifyArgs(args) {
|
|
63
75
|
let reason;
|
|
76
|
+
let fromClassification;
|
|
64
77
|
let fromPlan;
|
|
65
78
|
let writePlan;
|
|
79
|
+
let reproEvidence;
|
|
80
|
+
let externalEvidence;
|
|
66
81
|
let json = false;
|
|
67
82
|
let planOnly = false;
|
|
68
83
|
let changed = false;
|
|
@@ -92,21 +107,86 @@ function parseVerifyArgs(args) {
|
|
|
92
107
|
if (arg === '--from-plan') {
|
|
93
108
|
const value = args[index + 1];
|
|
94
109
|
if (!value || value.startsWith('-')) {
|
|
95
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
110
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
96
111
|
}
|
|
97
112
|
fromPlan = value;
|
|
98
113
|
index += 1;
|
|
99
114
|
continue;
|
|
100
115
|
}
|
|
116
|
+
if (arg === '--from-classification') {
|
|
117
|
+
const value = args[index + 1];
|
|
118
|
+
if (!value || value.startsWith('-')) {
|
|
119
|
+
return {
|
|
120
|
+
json,
|
|
121
|
+
planOnly,
|
|
122
|
+
changed,
|
|
123
|
+
reason,
|
|
124
|
+
fromClassification,
|
|
125
|
+
fromPlan,
|
|
126
|
+
error: 'missing_from_classification_value',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
fromClassification = value;
|
|
130
|
+
index += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
101
133
|
if (arg === '--write-plan') {
|
|
102
134
|
const value = args[index + 1];
|
|
103
135
|
if (!value || value.startsWith('-')) {
|
|
104
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
json,
|
|
138
|
+
planOnly,
|
|
139
|
+
changed,
|
|
140
|
+
reason,
|
|
141
|
+
fromClassification,
|
|
142
|
+
fromPlan,
|
|
143
|
+
writePlan,
|
|
144
|
+
error: 'missing_write_plan_value',
|
|
145
|
+
};
|
|
105
146
|
}
|
|
106
147
|
writePlan = value;
|
|
107
148
|
index += 1;
|
|
108
149
|
continue;
|
|
109
150
|
}
|
|
151
|
+
if (arg === '--external-evidence') {
|
|
152
|
+
const value = args[index + 1];
|
|
153
|
+
if (!value || value.startsWith('-')) {
|
|
154
|
+
return {
|
|
155
|
+
json,
|
|
156
|
+
planOnly,
|
|
157
|
+
changed,
|
|
158
|
+
reason,
|
|
159
|
+
fromClassification,
|
|
160
|
+
fromPlan,
|
|
161
|
+
writePlan,
|
|
162
|
+
externalEvidence,
|
|
163
|
+
error: 'missing_external_evidence_value',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
externalEvidence = value;
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (arg === '--repro-evidence') {
|
|
171
|
+
const value = args[index + 1];
|
|
172
|
+
if (!value || value.startsWith('-')) {
|
|
173
|
+
return {
|
|
174
|
+
json,
|
|
175
|
+
planOnly,
|
|
176
|
+
changed,
|
|
177
|
+
reason,
|
|
178
|
+
fromClassification,
|
|
179
|
+
fromPlan,
|
|
180
|
+
writePlan,
|
|
181
|
+
reproEvidence,
|
|
182
|
+
externalEvidence,
|
|
183
|
+
error: 'missing_repro_evidence_value',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
reproEvidence = value;
|
|
187
|
+
index += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
110
190
|
if (arg.startsWith('--reason=')) {
|
|
111
191
|
const value = arg.slice('--reason='.length);
|
|
112
192
|
if (value.length === 0) {
|
|
@@ -118,25 +198,98 @@ function parseVerifyArgs(args) {
|
|
|
118
198
|
if (arg.startsWith('--from-plan=')) {
|
|
119
199
|
const value = arg.slice('--from-plan='.length);
|
|
120
200
|
if (value.length === 0) {
|
|
121
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
201
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
122
202
|
}
|
|
123
203
|
fromPlan = value;
|
|
124
204
|
continue;
|
|
125
205
|
}
|
|
206
|
+
if (arg.startsWith('--from-classification=')) {
|
|
207
|
+
const value = arg.slice('--from-classification='.length);
|
|
208
|
+
if (value.length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
json,
|
|
211
|
+
planOnly,
|
|
212
|
+
changed,
|
|
213
|
+
reason,
|
|
214
|
+
fromClassification,
|
|
215
|
+
fromPlan,
|
|
216
|
+
error: 'missing_from_classification_value',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
fromClassification = value;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
126
222
|
if (arg.startsWith('--write-plan=')) {
|
|
127
223
|
const value = arg.slice('--write-plan='.length);
|
|
128
224
|
if (value.length === 0) {
|
|
129
|
-
return {
|
|
225
|
+
return {
|
|
226
|
+
json,
|
|
227
|
+
planOnly,
|
|
228
|
+
changed,
|
|
229
|
+
reason,
|
|
230
|
+
fromClassification,
|
|
231
|
+
fromPlan,
|
|
232
|
+
writePlan,
|
|
233
|
+
error: 'missing_write_plan_value',
|
|
234
|
+
};
|
|
130
235
|
}
|
|
131
236
|
writePlan = value;
|
|
132
237
|
continue;
|
|
133
238
|
}
|
|
239
|
+
if (arg.startsWith('--external-evidence=')) {
|
|
240
|
+
const value = arg.slice('--external-evidence='.length);
|
|
241
|
+
if (value.length === 0) {
|
|
242
|
+
return {
|
|
243
|
+
json,
|
|
244
|
+
planOnly,
|
|
245
|
+
changed,
|
|
246
|
+
reason,
|
|
247
|
+
fromClassification,
|
|
248
|
+
fromPlan,
|
|
249
|
+
writePlan,
|
|
250
|
+
externalEvidence,
|
|
251
|
+
error: 'missing_external_evidence_value',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
externalEvidence = value;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (arg.startsWith('--repro-evidence=')) {
|
|
258
|
+
const value = arg.slice('--repro-evidence='.length);
|
|
259
|
+
if (value.length === 0) {
|
|
260
|
+
return {
|
|
261
|
+
json,
|
|
262
|
+
planOnly,
|
|
263
|
+
changed,
|
|
264
|
+
reason,
|
|
265
|
+
fromClassification,
|
|
266
|
+
fromPlan,
|
|
267
|
+
writePlan,
|
|
268
|
+
reproEvidence,
|
|
269
|
+
externalEvidence,
|
|
270
|
+
error: 'missing_repro_evidence_value',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
reproEvidence = value;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
134
276
|
if (arg.startsWith('-')) {
|
|
135
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan, error: arg };
|
|
277
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence, error: arg };
|
|
136
278
|
}
|
|
137
|
-
return {
|
|
279
|
+
return {
|
|
280
|
+
json,
|
|
281
|
+
planOnly,
|
|
282
|
+
changed,
|
|
283
|
+
reason,
|
|
284
|
+
fromClassification,
|
|
285
|
+
fromPlan,
|
|
286
|
+
writePlan,
|
|
287
|
+
reproEvidence,
|
|
288
|
+
externalEvidence,
|
|
289
|
+
error: `unexpected:${arg}`,
|
|
290
|
+
};
|
|
138
291
|
}
|
|
139
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan };
|
|
292
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence };
|
|
140
293
|
}
|
|
141
294
|
function uniqueStrings(values) {
|
|
142
295
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
@@ -307,6 +460,285 @@ export function readInputFromPlan(projectRoot, inputPath) {
|
|
|
307
460
|
classificationReport,
|
|
308
461
|
};
|
|
309
462
|
}
|
|
463
|
+
function isExternalEvidenceStatus(value) {
|
|
464
|
+
return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
|
|
465
|
+
}
|
|
466
|
+
function isLegacyReproEvidenceStatus(value) {
|
|
467
|
+
return value === 'present' || value === 'unavailable' || value === 'missing';
|
|
468
|
+
}
|
|
469
|
+
function isReproBeforeFixStatus(value) {
|
|
470
|
+
return value === 'reproduced' || value === 'unavailable' || value === 'missing';
|
|
471
|
+
}
|
|
472
|
+
function isReproBeforeFixOutcome(value) {
|
|
473
|
+
return value === 'failed_as_expected' || value === 'failed_differently' || value === 'passed_unexpectedly' || value === null;
|
|
474
|
+
}
|
|
475
|
+
function isReproAfterFixStatus(value) {
|
|
476
|
+
return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
|
|
477
|
+
}
|
|
478
|
+
function isReproAfterFixOutcome(value) {
|
|
479
|
+
return value === 'passed_expected_behavior' || value === 'failed_same_route' || value === 'failed_differently' || value === null;
|
|
480
|
+
}
|
|
481
|
+
function isReproRegressionGuardStatus(value) {
|
|
482
|
+
return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
|
|
483
|
+
}
|
|
484
|
+
function isReproRouteKind(value) {
|
|
485
|
+
return (value === 'test' ||
|
|
486
|
+
value === 'cli' ||
|
|
487
|
+
value === 'browser' ||
|
|
488
|
+
value === 'api' ||
|
|
489
|
+
value === 'manual' ||
|
|
490
|
+
value === 'unknown' ||
|
|
491
|
+
value === null);
|
|
492
|
+
}
|
|
493
|
+
function readOptionalString(value) {
|
|
494
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
495
|
+
}
|
|
496
|
+
function readRouteStep(value, index) {
|
|
497
|
+
if (!isPlainRecord(value)) {
|
|
498
|
+
return {
|
|
499
|
+
ordinal: index + 1,
|
|
500
|
+
action: null,
|
|
501
|
+
target: null,
|
|
502
|
+
input_digest: null,
|
|
503
|
+
observation_digest: null,
|
|
504
|
+
summary: null,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const ordinal = typeof value.ordinal === 'number' && Number.isInteger(value.ordinal) && value.ordinal > 0 ? value.ordinal : index + 1;
|
|
508
|
+
return {
|
|
509
|
+
ordinal,
|
|
510
|
+
action: readOptionalString(value.action),
|
|
511
|
+
target: readOptionalString(value.target),
|
|
512
|
+
input_digest: readOptionalString(value.input_digest),
|
|
513
|
+
observation_digest: readOptionalString(value.observation_digest),
|
|
514
|
+
summary: readOptionalString(value.summary),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function readReproductionRoute(value) {
|
|
518
|
+
if (!isPlainRecord(value)) {
|
|
519
|
+
return {
|
|
520
|
+
route_id: null,
|
|
521
|
+
route_kind: null,
|
|
522
|
+
route_digest: null,
|
|
523
|
+
failure_oracle_hash: null,
|
|
524
|
+
steps: [],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const routeKind = value.route_kind ?? null;
|
|
528
|
+
if (!isReproRouteKind(routeKind)) {
|
|
529
|
+
throw new Error('invalid_repro_evidence_file');
|
|
530
|
+
}
|
|
531
|
+
const rawSteps = Array.isArray(value.steps) ? value.steps : [];
|
|
532
|
+
return {
|
|
533
|
+
route_id: readOptionalString(value.route_id),
|
|
534
|
+
route_kind: routeKind,
|
|
535
|
+
route_digest: readOptionalString(value.route_digest),
|
|
536
|
+
failure_oracle_hash: readOptionalString(value.failure_oracle_hash),
|
|
537
|
+
steps: rawSteps.map((step, index) => readRouteStep(step, index)),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function readLegacyReproEvidenceItem(value) {
|
|
541
|
+
if (!isPlainRecord(value)) {
|
|
542
|
+
return {
|
|
543
|
+
status: 'missing',
|
|
544
|
+
summary: null,
|
|
545
|
+
reason: null,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
if (!isLegacyReproEvidenceStatus(value.status)) {
|
|
549
|
+
throw new Error('invalid_repro_evidence_file');
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
status: value.status,
|
|
553
|
+
summary: readOptionalString(value.summary),
|
|
554
|
+
reason: readOptionalString(value.reason),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function legacyBeforeFixEvidence(value) {
|
|
558
|
+
const item = readLegacyReproEvidenceItem(value);
|
|
559
|
+
return {
|
|
560
|
+
status: item.status === 'present' ? 'reproduced' : item.status,
|
|
561
|
+
outcome: item.status === 'present' ? 'failed_as_expected' : null,
|
|
562
|
+
receipt_path: null,
|
|
563
|
+
receipt_sha256: null,
|
|
564
|
+
verification_plan_id: null,
|
|
565
|
+
summary: item.summary,
|
|
566
|
+
reason: item.reason,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function legacyAfterFixEvidence(value) {
|
|
570
|
+
const item = readLegacyReproEvidenceItem(value);
|
|
571
|
+
return {
|
|
572
|
+
status: item.status === 'present' ? 'passed' : item.status,
|
|
573
|
+
outcome: item.status === 'present' ? 'passed_expected_behavior' : null,
|
|
574
|
+
same_route_as: null,
|
|
575
|
+
receipt_path: null,
|
|
576
|
+
receipt_sha256: null,
|
|
577
|
+
verification_plan_id: null,
|
|
578
|
+
summary: item.summary,
|
|
579
|
+
reason: item.reason,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function legacyRegressionGuardEvidence(value) {
|
|
583
|
+
const item = readLegacyReproEvidenceItem(value);
|
|
584
|
+
return {
|
|
585
|
+
status: item.status === 'present' ? 'passed' : item.status,
|
|
586
|
+
intent: null,
|
|
587
|
+
test_path: null,
|
|
588
|
+
receipt_path: null,
|
|
589
|
+
receipt_sha256: null,
|
|
590
|
+
verification_plan_id: null,
|
|
591
|
+
summary: item.summary,
|
|
592
|
+
reason: item.reason,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function readBeforeFixEvidence(value) {
|
|
596
|
+
if (!isPlainRecord(value)) {
|
|
597
|
+
return {
|
|
598
|
+
status: 'missing',
|
|
599
|
+
outcome: null,
|
|
600
|
+
receipt_path: null,
|
|
601
|
+
receipt_sha256: null,
|
|
602
|
+
verification_plan_id: null,
|
|
603
|
+
summary: null,
|
|
604
|
+
reason: null,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const outcome = value.outcome ?? null;
|
|
608
|
+
if (!isReproBeforeFixStatus(value.status) || !isReproBeforeFixOutcome(outcome)) {
|
|
609
|
+
throw new Error('invalid_repro_evidence_file');
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
status: value.status,
|
|
613
|
+
outcome,
|
|
614
|
+
receipt_path: readOptionalString(value.receipt_path),
|
|
615
|
+
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
616
|
+
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
617
|
+
summary: readOptionalString(value.summary),
|
|
618
|
+
reason: readOptionalString(value.reason),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function readAfterFixEvidence(value) {
|
|
622
|
+
if (!isPlainRecord(value)) {
|
|
623
|
+
return {
|
|
624
|
+
status: 'missing',
|
|
625
|
+
outcome: null,
|
|
626
|
+
same_route_as: null,
|
|
627
|
+
receipt_path: null,
|
|
628
|
+
receipt_sha256: null,
|
|
629
|
+
verification_plan_id: null,
|
|
630
|
+
summary: null,
|
|
631
|
+
reason: null,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const outcome = value.outcome ?? null;
|
|
635
|
+
if (!isReproAfterFixStatus(value.status) || !isReproAfterFixOutcome(outcome)) {
|
|
636
|
+
throw new Error('invalid_repro_evidence_file');
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
status: value.status,
|
|
640
|
+
outcome,
|
|
641
|
+
same_route_as: readOptionalString(value.same_route_as),
|
|
642
|
+
receipt_path: readOptionalString(value.receipt_path),
|
|
643
|
+
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
644
|
+
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
645
|
+
summary: readOptionalString(value.summary),
|
|
646
|
+
reason: readOptionalString(value.reason),
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function readRegressionGuardEvidence(value) {
|
|
650
|
+
if (!isPlainRecord(value)) {
|
|
651
|
+
return {
|
|
652
|
+
status: 'missing',
|
|
653
|
+
intent: null,
|
|
654
|
+
test_path: null,
|
|
655
|
+
receipt_path: null,
|
|
656
|
+
receipt_sha256: null,
|
|
657
|
+
verification_plan_id: null,
|
|
658
|
+
summary: null,
|
|
659
|
+
reason: null,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (!isReproRegressionGuardStatus(value.status)) {
|
|
663
|
+
throw new Error('invalid_repro_evidence_file');
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
status: value.status,
|
|
667
|
+
intent: readOptionalString(value.intent),
|
|
668
|
+
test_path: readOptionalString(value.test_path),
|
|
669
|
+
receipt_path: readOptionalString(value.receipt_path),
|
|
670
|
+
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
671
|
+
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
672
|
+
summary: readOptionalString(value.summary),
|
|
673
|
+
reason: readOptionalString(value.reason),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function readReproEvidenceFile(projectRoot, inputPath) {
|
|
677
|
+
let parsed;
|
|
678
|
+
const evidencePath = resolvePlanPath(projectRoot, inputPath);
|
|
679
|
+
try {
|
|
680
|
+
parsed = JSON.parse(readFileSync(evidencePath, 'utf8'));
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
throw new Error('invalid_repro_evidence_file');
|
|
684
|
+
}
|
|
685
|
+
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
|
|
686
|
+
throw new Error('unsupported_repro_evidence_source');
|
|
687
|
+
}
|
|
688
|
+
const regressionGuard = isPlainRecord(parsed.regression_guard) && isReproRegressionGuardStatus(parsed.regression_guard.status)
|
|
689
|
+
? readRegressionGuardEvidence(parsed.regression_guard)
|
|
690
|
+
: legacyRegressionGuardEvidence(parsed.regression_guard);
|
|
691
|
+
return {
|
|
692
|
+
source: 'repro_first_debug',
|
|
693
|
+
authority: 'claim_evidence',
|
|
694
|
+
reported_symptom: readOptionalString(parsed.reported_symptom),
|
|
695
|
+
expected_behavior: readOptionalString(parsed.expected_behavior),
|
|
696
|
+
observed_behavior: readOptionalString(parsed.observed_behavior),
|
|
697
|
+
reproduction_route: readReproductionRoute(parsed.reproduction_route),
|
|
698
|
+
before_fix: isPlainRecord(parsed.before_fix)
|
|
699
|
+
? readBeforeFixEvidence(parsed.before_fix)
|
|
700
|
+
: legacyBeforeFixEvidence(parsed.evidence_before_fix),
|
|
701
|
+
after_fix: isPlainRecord(parsed.after_fix)
|
|
702
|
+
? readAfterFixEvidence(parsed.after_fix)
|
|
703
|
+
: legacyAfterFixEvidence(parsed.evidence_after_fix),
|
|
704
|
+
regression_guard: regressionGuard,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function readExternalEvidenceFile(projectRoot, inputPath) {
|
|
708
|
+
let parsed;
|
|
709
|
+
const evidencePath = resolvePlanPath(projectRoot, inputPath);
|
|
710
|
+
try {
|
|
711
|
+
parsed = JSON.parse(readFileSync(evidencePath, 'utf8'));
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
throw new Error('invalid_external_evidence_file');
|
|
715
|
+
}
|
|
716
|
+
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
|
|
717
|
+
throw new Error('unsupported_external_evidence_source');
|
|
718
|
+
}
|
|
719
|
+
if (!Array.isArray(parsed.checks)) {
|
|
720
|
+
throw new Error('invalid_external_evidence_file');
|
|
721
|
+
}
|
|
722
|
+
return parsed.checks.map((check) => {
|
|
723
|
+
if (!isPlainRecord(check) ||
|
|
724
|
+
typeof check.provider !== 'string' ||
|
|
725
|
+
check.provider.length === 0 ||
|
|
726
|
+
typeof check.name !== 'string' ||
|
|
727
|
+
check.name.length === 0 ||
|
|
728
|
+
!isExternalEvidenceStatus(check.status)) {
|
|
729
|
+
throw new Error('invalid_external_evidence_file');
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
source: 'external_ci',
|
|
733
|
+
authority: 'supporting_only',
|
|
734
|
+
provider: check.provider,
|
|
735
|
+
name: check.name,
|
|
736
|
+
status: check.status,
|
|
737
|
+
url: readOptionalString(check.url),
|
|
738
|
+
summary: readOptionalString(check.summary),
|
|
739
|
+
};
|
|
740
|
+
});
|
|
741
|
+
}
|
|
310
742
|
function createInputFromChanged(projectRoot) {
|
|
311
743
|
const plan = createClassifyOutput(projectRoot, 'changed', []);
|
|
312
744
|
return {
|
|
@@ -344,6 +776,9 @@ function skippedResult(candidate) {
|
|
|
344
776
|
reason: candidate.reason,
|
|
345
777
|
detail: candidate.detail,
|
|
346
778
|
exit_code: null,
|
|
779
|
+
verification_plan_id: null,
|
|
780
|
+
receipt_path: null,
|
|
781
|
+
receipt_sha256: null,
|
|
347
782
|
receipt: null,
|
|
348
783
|
};
|
|
349
784
|
}
|
|
@@ -383,7 +818,7 @@ function testTargetsByScheduledIntent(report) {
|
|
|
383
818
|
candidate.appliedTestTargets.length > 0)
|
|
384
819
|
.map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
|
|
385
820
|
}
|
|
386
|
-
async function runVerificationIntent(intent, lang, testTargets = []) {
|
|
821
|
+
async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = []) {
|
|
387
822
|
const output = createBufferedOutput();
|
|
388
823
|
const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
|
|
389
824
|
writeLatestReceipt: false,
|
|
@@ -413,6 +848,9 @@ async function runVerificationIntent(intent, lang, testTargets = []) {
|
|
|
413
848
|
reason: exitCode === 0 ? null : 'run_failed',
|
|
414
849
|
detail: output.stderr().trim() || null,
|
|
415
850
|
exit_code: exitCode,
|
|
851
|
+
verification_plan_id: verificationPlanId,
|
|
852
|
+
receipt_path: null,
|
|
853
|
+
receipt_sha256: null,
|
|
416
854
|
receipt,
|
|
417
855
|
};
|
|
418
856
|
}
|
|
@@ -430,6 +868,210 @@ function summarizeResults(results) {
|
|
|
430
868
|
skipped,
|
|
431
869
|
};
|
|
432
870
|
}
|
|
871
|
+
function countUndeclaredWriteDrift(results) {
|
|
872
|
+
return results.filter((result) => {
|
|
873
|
+
const writeDrift = result.receipt?.write_drift;
|
|
874
|
+
if (typeof writeDrift !== 'object' || writeDrift === null) {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
return writeDrift.has_undeclared_changes === true;
|
|
878
|
+
}).length;
|
|
879
|
+
}
|
|
880
|
+
function stringField(value) {
|
|
881
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
882
|
+
}
|
|
883
|
+
function objectField(value) {
|
|
884
|
+
return typeof value === 'object' && value !== null ? value : null;
|
|
885
|
+
}
|
|
886
|
+
function performanceForResult(result) {
|
|
887
|
+
return objectField(result.receipt?.performance);
|
|
888
|
+
}
|
|
889
|
+
function resultSummaryForResult(result) {
|
|
890
|
+
return objectField(performanceForResult(result)?.result_summary);
|
|
891
|
+
}
|
|
892
|
+
function commandFingerprintForResult(result) {
|
|
893
|
+
return stringField(performanceForResult(result)?.command_fingerprint);
|
|
894
|
+
}
|
|
895
|
+
function exitCodeClassForResult(result) {
|
|
896
|
+
const resultSummary = resultSummaryForResult(result);
|
|
897
|
+
const explicitClass = stringField(resultSummary?.exit_code_class);
|
|
898
|
+
if (explicitClass) {
|
|
899
|
+
return explicitClass;
|
|
900
|
+
}
|
|
901
|
+
if (result.exit_code === null) {
|
|
902
|
+
return 'no_exit_code';
|
|
903
|
+
}
|
|
904
|
+
return result.exit_code === 0 ? 'success' : 'failure';
|
|
905
|
+
}
|
|
906
|
+
function timedOutForResult(result) {
|
|
907
|
+
const resultSummary = resultSummaryForResult(result);
|
|
908
|
+
return result.status === 'timed_out' || resultSummary?.timed_out === true;
|
|
909
|
+
}
|
|
910
|
+
function errorKindForResult(result) {
|
|
911
|
+
return stringField(resultSummaryForResult(result)?.error_kind) ?? (result.status === 'start_failed' ? 'start_failed' : null);
|
|
912
|
+
}
|
|
913
|
+
function failedResults(results) {
|
|
914
|
+
return results.filter((result) => !result.skipped &&
|
|
915
|
+
(result.status === 'failed' || result.status === 'timed_out' || result.status === 'start_failed'));
|
|
916
|
+
}
|
|
917
|
+
function createFailureFingerprintForVerify(input) {
|
|
918
|
+
const failures = failedResults(input.results);
|
|
919
|
+
return createVerificationFailureFingerprint({
|
|
920
|
+
verificationPlanId: input.verificationPlanId,
|
|
921
|
+
failedIntents: failures.map((result) => result.intent).filter((intent) => intent !== null),
|
|
922
|
+
exitCodeClasses: failures.map(exitCodeClassForResult).filter((value) => value !== null),
|
|
923
|
+
timeoutFlags: failures.map(timedOutForResult),
|
|
924
|
+
errorKinds: failures.map(errorKindForResult).filter((value) => value !== null),
|
|
925
|
+
riskCodes: input.riskCodes,
|
|
926
|
+
affectedSurfaces: input.report.requirements.flatMap((requirement) => requirement.surfaces),
|
|
927
|
+
commandFingerprints: failures.map(commandFingerprintForResult).filter((value) => value !== null),
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
function riskCodesForFailureFingerprint(input) {
|
|
931
|
+
const writeDriftRiskCodes = countUndeclaredWriteDrift(input.results) > 0 ? ['undeclared_write_drift'] : [];
|
|
932
|
+
return [
|
|
933
|
+
...input.sourceAnchorRisks.map(() => 'source_anchor_invariant_review_required'),
|
|
934
|
+
...input.scopeDiffRisks.map((risk) => risk.code),
|
|
935
|
+
...input.validationRatchetRisks.map((risk) => risk.code),
|
|
936
|
+
...input.reproEvidenceRisks.map((risk) => risk.code),
|
|
937
|
+
...input.externalEvidenceRisks.map((risk) => risk.code),
|
|
938
|
+
...writeDriftRiskCodes,
|
|
939
|
+
];
|
|
940
|
+
}
|
|
941
|
+
function createReceiptBindingEvidence(results, verificationPlanId) {
|
|
942
|
+
let planBoundCount = 0;
|
|
943
|
+
let planUnboundCount = 0;
|
|
944
|
+
let fingerprintBoundCount = 0;
|
|
945
|
+
let fingerprintUnboundCount = 0;
|
|
946
|
+
let currentStateBoundCount = 0;
|
|
947
|
+
let currentStateUnavailableCount = 0;
|
|
948
|
+
let staleCount = 0;
|
|
949
|
+
let planMismatchCount = 0;
|
|
950
|
+
for (const result of results) {
|
|
951
|
+
if (!result.receipt) {
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
const receiptPlanId = stringField(result.receipt.verification_plan_id);
|
|
955
|
+
const resultPlanId = result.verification_plan_id;
|
|
956
|
+
const resultPlanMatches = resultPlanId === verificationPlanId;
|
|
957
|
+
const receiptPlanMatches = receiptPlanId === verificationPlanId;
|
|
958
|
+
if (resultPlanMatches && receiptPlanMatches) {
|
|
959
|
+
planBoundCount += 1;
|
|
960
|
+
}
|
|
961
|
+
else if ((typeof resultPlanId === 'string' && resultPlanId !== verificationPlanId) ||
|
|
962
|
+
(receiptPlanId !== null && receiptPlanId !== verificationPlanId)) {
|
|
963
|
+
planMismatchCount += 1;
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
planUnboundCount += 1;
|
|
967
|
+
}
|
|
968
|
+
const performance = objectField(result.receipt.performance);
|
|
969
|
+
const hasFingerprints = performance !== null &&
|
|
970
|
+
stringField(performance.command_fingerprint) !== null &&
|
|
971
|
+
stringField(performance.intent_fingerprint) !== null &&
|
|
972
|
+
stringField(performance.contract_fingerprint) !== null;
|
|
973
|
+
if (hasFingerprints) {
|
|
974
|
+
fingerprintBoundCount += 1;
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
fingerprintUnboundCount += 1;
|
|
978
|
+
}
|
|
979
|
+
const currentStateBinding = stringField(result.receipt.head_tree_hash) ??
|
|
980
|
+
stringField(result.receipt.changed_files_hash) ??
|
|
981
|
+
stringField(result.receipt.current_state_hash);
|
|
982
|
+
if (currentStateBinding !== null) {
|
|
983
|
+
currentStateBoundCount += 1;
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
currentStateUnavailableCount += 1;
|
|
987
|
+
}
|
|
988
|
+
if (!result.receipt_path || !result.receipt_sha256) {
|
|
989
|
+
staleCount += 1;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
plan_bound_count: planBoundCount,
|
|
994
|
+
plan_unbound_count: planUnboundCount,
|
|
995
|
+
fingerprint_bound_count: fingerprintBoundCount,
|
|
996
|
+
fingerprint_unbound_count: fingerprintUnboundCount,
|
|
997
|
+
current_state_bound_count: currentStateBoundCount,
|
|
998
|
+
current_state_unavailable_count: currentStateUnavailableCount,
|
|
999
|
+
stale_count: staleCount,
|
|
1000
|
+
plan_mismatch_count: planMismatchCount,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function resultForSelectedIntent(results, intent) {
|
|
1004
|
+
return results.find((result) => result.intent === intent && result.status !== 'skipped') ?? null;
|
|
1005
|
+
}
|
|
1006
|
+
function createCriteriaEvidence(report, results) {
|
|
1007
|
+
const evidence = {
|
|
1008
|
+
total: report.requirements.length,
|
|
1009
|
+
covered: 0,
|
|
1010
|
+
partially_covered: 0,
|
|
1011
|
+
uncovered: 0,
|
|
1012
|
+
blocked: 0,
|
|
1013
|
+
contradicted: 0,
|
|
1014
|
+
};
|
|
1015
|
+
return report.requirements.reduce((current, requirement) => {
|
|
1016
|
+
const candidates = report.candidates.filter((candidate) => candidate.reason === requirement.reason);
|
|
1017
|
+
const selectedIntents = candidates
|
|
1018
|
+
.filter((candidate) => candidate.selectionState === 'selected')
|
|
1019
|
+
.map((candidate) => candidate.intent)
|
|
1020
|
+
.filter((intent) => intent !== null);
|
|
1021
|
+
const skippedIntents = candidates
|
|
1022
|
+
.filter((candidate) => candidate.status !== 'runnable')
|
|
1023
|
+
.map((candidate) => candidate.intent)
|
|
1024
|
+
.filter((intent) => intent !== null);
|
|
1025
|
+
const gapCount = report.gaps.filter((gap) => gap.reason === requirement.reason).length;
|
|
1026
|
+
const selectedResults = selectedIntents.map((intent) => resultForSelectedIntent(results, intent));
|
|
1027
|
+
if (selectedResults.some((result) => result?.status === 'failed' || result?.status === 'timed_out' || result?.status === 'start_failed')) {
|
|
1028
|
+
return { ...current, contradicted: current.contradicted + 1 };
|
|
1029
|
+
}
|
|
1030
|
+
if (gapCount > 0 || (selectedIntents.length === 0 && skippedIntents.length > 0)) {
|
|
1031
|
+
return { ...current, blocked: current.blocked + 1 };
|
|
1032
|
+
}
|
|
1033
|
+
if (selectedIntents.length === 0) {
|
|
1034
|
+
return { ...current, uncovered: current.uncovered + 1 };
|
|
1035
|
+
}
|
|
1036
|
+
if (skippedIntents.length > 0) {
|
|
1037
|
+
return { ...current, partially_covered: current.partially_covered + 1 };
|
|
1038
|
+
}
|
|
1039
|
+
if (selectedResults.every((result) => result?.status === 'passed')) {
|
|
1040
|
+
return { ...current, covered: current.covered + 1 };
|
|
1041
|
+
}
|
|
1042
|
+
return { ...current, uncovered: current.uncovered + 1 };
|
|
1043
|
+
}, evidence);
|
|
1044
|
+
}
|
|
1045
|
+
function createCompletionVerdictForResults(input) {
|
|
1046
|
+
const receiptBinding = createReceiptBindingEvidence(input.results, input.verificationPlanId);
|
|
1047
|
+
const receiptBindingRiskCount = receiptBinding.plan_unbound_count + receiptBinding.fingerprint_unbound_count;
|
|
1048
|
+
const repeatedFailureBlockerCount = input.repeatedFailureRisks.filter((risk) => risk.verdict_effect === 'blocker').length;
|
|
1049
|
+
return createVerifyCompletionVerdict({
|
|
1050
|
+
verificationPlanId: input.verificationPlanId,
|
|
1051
|
+
matchedIntents: input.summary.matched,
|
|
1052
|
+
ranIntents: input.summary.ran,
|
|
1053
|
+
passedIntents: input.summary.passed,
|
|
1054
|
+
failedIntents: input.summary.failed,
|
|
1055
|
+
skippedIntents: input.summary.skipped,
|
|
1056
|
+
receiptCount: input.results.filter((result) => result.receipt !== null).length,
|
|
1057
|
+
sourceAnchorRiskCount: input.sourceAnchorRiskCount,
|
|
1058
|
+
scopeDiffRiskCount: input.scopeDiffRiskCount,
|
|
1059
|
+
repeatedFailureCount: input.repeatedFailureRisks.length,
|
|
1060
|
+
repeatedFailureBlockerCount,
|
|
1061
|
+
validationRatchetRiskCount: input.validationRatchetRiskCount,
|
|
1062
|
+
validationRatchetContradictionCount: input.validationRatchetContradictionCount,
|
|
1063
|
+
reproEvidenceRiskCount: input.reproEvidenceRiskCount,
|
|
1064
|
+
reproEvidenceContradictionCount: input.reproEvidenceContradictionCount,
|
|
1065
|
+
reproEvidenceUnverifiedCount: input.reproEvidenceUnverifiedCount,
|
|
1066
|
+
externalEvidenceRiskCount: input.externalEvidenceRiskCount,
|
|
1067
|
+
writeDriftRiskCount: countUndeclaredWriteDrift(input.results),
|
|
1068
|
+
receiptBindingRiskCount,
|
|
1069
|
+
staleReceiptCount: receiptBinding.stale_count,
|
|
1070
|
+
planMismatchCount: receiptBinding.plan_mismatch_count,
|
|
1071
|
+
criteria: createCriteriaEvidence(input.report, input.results),
|
|
1072
|
+
receiptBinding,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
433
1075
|
function getVerificationStatus(summary) {
|
|
434
1076
|
if (summary.failed > 0) {
|
|
435
1077
|
return 'failed';
|
|
@@ -442,35 +1084,223 @@ function getVerificationStatus(summary) {
|
|
|
442
1084
|
}
|
|
443
1085
|
return 'passed';
|
|
444
1086
|
}
|
|
445
|
-
function
|
|
1087
|
+
function isVerificationStatus(value) {
|
|
1088
|
+
return value === 'passed' || value === 'partial' || value === 'failed' || value === 'blocked';
|
|
1089
|
+
}
|
|
1090
|
+
function readVerificationFailureFingerprint(value) {
|
|
1091
|
+
const record = objectField(value);
|
|
1092
|
+
if (record?.schema_version !== '1' ||
|
|
1093
|
+
typeof record.fingerprint !== 'string' ||
|
|
1094
|
+
typeof record.verification_plan_id !== 'string' ||
|
|
1095
|
+
typeof record.failed_intents_hash !== 'string' ||
|
|
1096
|
+
typeof record.exit_code_classes_hash !== 'string' ||
|
|
1097
|
+
typeof record.timeout_flags_hash !== 'string' ||
|
|
1098
|
+
typeof record.error_kinds_hash !== 'string' ||
|
|
1099
|
+
typeof record.diagnostic_hash !== 'string' ||
|
|
1100
|
+
typeof record.risk_codes_hash !== 'string' ||
|
|
1101
|
+
typeof record.affected_surfaces_hash !== 'string' ||
|
|
1102
|
+
typeof record.command_fingerprints_hash !== 'string') {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
schema_version: '1',
|
|
1107
|
+
fingerprint: record.fingerprint,
|
|
1108
|
+
verification_plan_id: record.verification_plan_id,
|
|
1109
|
+
failed_intents_hash: record.failed_intents_hash,
|
|
1110
|
+
exit_code_classes_hash: record.exit_code_classes_hash,
|
|
1111
|
+
timeout_flags_hash: record.timeout_flags_hash,
|
|
1112
|
+
error_kinds_hash: record.error_kinds_hash,
|
|
1113
|
+
diagnostic_hash: record.diagnostic_hash,
|
|
1114
|
+
risk_codes_hash: record.risk_codes_hash,
|
|
1115
|
+
affected_surfaces_hash: record.affected_surfaces_hash,
|
|
1116
|
+
command_fingerprints_hash: record.command_fingerprints_hash,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function readPreviousVerifyLatestSummary(projectRoot) {
|
|
1120
|
+
try {
|
|
1121
|
+
const parsed = JSON.parse(readFileSync(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), 'utf8'));
|
|
1122
|
+
if (parsed.command !== 'verify' ||
|
|
1123
|
+
parsed.kind !== 'verify_run_summary' ||
|
|
1124
|
+
typeof parsed.verification_plan_id !== 'string' ||
|
|
1125
|
+
!isVerificationStatus(parsed.status)) {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
verification_plan_id: parsed.verification_plan_id,
|
|
1130
|
+
status: parsed.status,
|
|
1131
|
+
failure_fingerprint: readVerificationFailureFingerprint(parsed.failure_fingerprint),
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function hashTextSha256(content) {
|
|
1139
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
1140
|
+
}
|
|
1141
|
+
function stableJson(value) {
|
|
1142
|
+
if (Array.isArray(value)) {
|
|
1143
|
+
return `[${value.map((entry) => stableJson(entry)).join(',')}]`;
|
|
1144
|
+
}
|
|
1145
|
+
if (value && typeof value === 'object') {
|
|
1146
|
+
const record = value;
|
|
1147
|
+
return `{${Object.keys(record)
|
|
1148
|
+
.sort((left, right) => left.localeCompare(right))
|
|
1149
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
|
|
1150
|
+
.join(',')}}`;
|
|
1151
|
+
}
|
|
1152
|
+
return JSON.stringify(value) ?? 'null';
|
|
1153
|
+
}
|
|
1154
|
+
function getCandidateIntentNames(report) {
|
|
1155
|
+
return [...new Set(report.candidates.map((candidate) => candidate.intent).filter((intent) => Boolean(intent)))]
|
|
1156
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1157
|
+
}
|
|
1158
|
+
function createVerificationPlanId(report, contract) {
|
|
1159
|
+
const relatedIntents = Object.fromEntries(getCandidateIntentNames(report).map((intent) => [intent, contract.intents[intent] ?? null]));
|
|
1160
|
+
const fingerprintSource = {
|
|
1161
|
+
schema_version: '1',
|
|
1162
|
+
algorithm: 'mustflow.verify_plan_id.v1',
|
|
1163
|
+
report: {
|
|
1164
|
+
source: report.source,
|
|
1165
|
+
files: report.files,
|
|
1166
|
+
classification_summary: report.classification_summary,
|
|
1167
|
+
requirements: report.requirements,
|
|
1168
|
+
candidates: report.candidates,
|
|
1169
|
+
gaps: report.gaps,
|
|
1170
|
+
schedule: report.schedule,
|
|
1171
|
+
test_selection: report.test_selection,
|
|
1172
|
+
},
|
|
1173
|
+
command_contract: {
|
|
1174
|
+
defaults: contract.defaults,
|
|
1175
|
+
resources: contract.resources,
|
|
1176
|
+
intents: relatedIntents,
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
return hashTextSha256(stableJson(fingerprintSource));
|
|
1180
|
+
}
|
|
1181
|
+
function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, validationRatchetRisks, reproEvidence, externalChecks) {
|
|
446
1182
|
const runDir = path.join(projectRoot, VERIFY_RUN_DIR);
|
|
447
1183
|
const intentDir = path.join(runDir, 'intents');
|
|
448
1184
|
const receipts = [];
|
|
1185
|
+
const results = [];
|
|
449
1186
|
rmSync(runDir, { recursive: true, force: true });
|
|
450
1187
|
mkdirSync(intentDir, { recursive: true });
|
|
451
1188
|
for (const [index, result] of output.results.entries()) {
|
|
452
1189
|
let receiptPath = null;
|
|
1190
|
+
let receiptSha256 = null;
|
|
1191
|
+
let receipt = result.receipt;
|
|
453
1192
|
if (result.intent && result.receipt) {
|
|
454
1193
|
const fileName = `${String(index + 1).padStart(3, '0')}-${sanitizeIntentFilePart(result.intent)}.json`;
|
|
455
1194
|
const absoluteReceiptPath = path.join(intentDir, fileName);
|
|
456
1195
|
receiptPath = toPosixPath(path.join(VERIFY_RUN_DIR, 'intents', fileName));
|
|
457
|
-
|
|
1196
|
+
receipt = {
|
|
1197
|
+
...result.receipt,
|
|
1198
|
+
verification_plan_id: output.verification_plan_id,
|
|
1199
|
+
receipt_path: receiptPath,
|
|
1200
|
+
};
|
|
1201
|
+
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
1202
|
+
receiptSha256 = hashTextSha256(receiptContent);
|
|
1203
|
+
writeFileSync(absoluteReceiptPath, receiptContent, 'utf8');
|
|
458
1204
|
}
|
|
459
1205
|
receipts.push({
|
|
460
1206
|
intent: result.intent,
|
|
461
1207
|
status: result.status,
|
|
462
1208
|
skipped: result.skipped,
|
|
1209
|
+
verification_plan_id: result.skipped ? null : output.verification_plan_id,
|
|
463
1210
|
receipt_path: receiptPath,
|
|
1211
|
+
receipt_sha256: receiptSha256,
|
|
1212
|
+
});
|
|
1213
|
+
results.push({
|
|
1214
|
+
...result,
|
|
1215
|
+
verification_plan_id: result.skipped ? null : output.verification_plan_id,
|
|
1216
|
+
receipt_path: receiptPath,
|
|
1217
|
+
receipt_sha256: receiptSha256,
|
|
1218
|
+
receipt,
|
|
464
1219
|
});
|
|
465
1220
|
}
|
|
1221
|
+
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, {
|
|
1222
|
+
verificationPlanId: output.verification_plan_id,
|
|
1223
|
+
});
|
|
1224
|
+
const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
|
|
1225
|
+
const validationRatchetVerdictEffects = countValidationRatchetVerdictEffects(validationRatchetRisks);
|
|
1226
|
+
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
1227
|
+
const failureFingerprint = createFailureFingerprintForVerify({
|
|
1228
|
+
verificationPlanId: output.verification_plan_id,
|
|
1229
|
+
report,
|
|
1230
|
+
results,
|
|
1231
|
+
riskCodes: riskCodesForFailureFingerprint({
|
|
1232
|
+
sourceAnchorRisks,
|
|
1233
|
+
scopeDiffRisks,
|
|
1234
|
+
validationRatchetRisks,
|
|
1235
|
+
reproEvidenceRisks,
|
|
1236
|
+
externalEvidenceRisks,
|
|
1237
|
+
results,
|
|
1238
|
+
}),
|
|
1239
|
+
});
|
|
1240
|
+
const repeatedFailureSummary = updateRepeatedFailureState({
|
|
1241
|
+
projectRoot,
|
|
1242
|
+
failureFingerprint,
|
|
1243
|
+
status: output.status,
|
|
1244
|
+
});
|
|
1245
|
+
const previousVerifyLatest = readPreviousVerifyLatestSummary(projectRoot);
|
|
1246
|
+
const finalRepeatedFailureRisks = createRepeatedFailureRisks({
|
|
1247
|
+
previousFailureFingerprint: previousVerifyLatest?.failure_fingerprint ?? null,
|
|
1248
|
+
previousStatus: previousVerifyLatest?.status ?? null,
|
|
1249
|
+
currentFailureFingerprint: failureFingerprint,
|
|
1250
|
+
currentStatus: output.status,
|
|
1251
|
+
currentSummary: repeatedFailureSummary,
|
|
1252
|
+
});
|
|
1253
|
+
const completionVerdict = createCompletionVerdictForResults({
|
|
1254
|
+
report,
|
|
1255
|
+
verificationPlanId: output.verification_plan_id,
|
|
1256
|
+
summary: output.summary,
|
|
1257
|
+
results,
|
|
1258
|
+
sourceAnchorRiskCount: sourceAnchorRisks.length,
|
|
1259
|
+
scopeDiffRiskCount: scopeDiffRisks.length,
|
|
1260
|
+
repeatedFailureRisks: finalRepeatedFailureRisks,
|
|
1261
|
+
validationRatchetRiskCount: validationRatchetRisks.length,
|
|
1262
|
+
validationRatchetContradictionCount: validationRatchetVerdictEffects.contradicted,
|
|
1263
|
+
reproEvidenceRiskCount: reproEvidenceRisks.length,
|
|
1264
|
+
reproEvidenceContradictionCount: reproEvidenceVerdictEffects.contradicted,
|
|
1265
|
+
reproEvidenceUnverifiedCount: reproEvidenceVerdictEffects.unverified,
|
|
1266
|
+
externalEvidenceRiskCount: externalEvidenceRisks.length,
|
|
1267
|
+
});
|
|
1268
|
+
const outputWithReceiptPaths = {
|
|
1269
|
+
...output,
|
|
1270
|
+
completion_verdict: completionVerdict,
|
|
1271
|
+
failure_fingerprint: failureFingerprint,
|
|
1272
|
+
repeated_failure_summary: repeatedFailureSummary,
|
|
1273
|
+
results,
|
|
1274
|
+
evidence_model: createVerifyEvidenceModel({
|
|
1275
|
+
report,
|
|
1276
|
+
results,
|
|
1277
|
+
verificationPlanId: output.verification_plan_id,
|
|
1278
|
+
verdict: completionVerdict,
|
|
1279
|
+
sourceAnchorRisks,
|
|
1280
|
+
scopeDiffRisks,
|
|
1281
|
+
repeatedFailureRisks: finalRepeatedFailureRisks,
|
|
1282
|
+
validationRatchetRisks,
|
|
1283
|
+
reproEvidence,
|
|
1284
|
+
reproEvidenceRisks,
|
|
1285
|
+
externalChecks,
|
|
1286
|
+
externalEvidenceRisks,
|
|
1287
|
+
}),
|
|
1288
|
+
};
|
|
466
1289
|
const manifest = {
|
|
467
1290
|
schema_version: '1',
|
|
468
1291
|
command: 'verify',
|
|
469
|
-
reason:
|
|
470
|
-
reasons:
|
|
471
|
-
plan_source:
|
|
472
|
-
|
|
473
|
-
|
|
1292
|
+
reason: outputWithReceiptPaths.reason,
|
|
1293
|
+
reasons: outputWithReceiptPaths.reasons,
|
|
1294
|
+
plan_source: outputWithReceiptPaths.plan_source,
|
|
1295
|
+
verification_plan_id: outputWithReceiptPaths.verification_plan_id,
|
|
1296
|
+
status: outputWithReceiptPaths.status,
|
|
1297
|
+
completion_verdict: outputWithReceiptPaths.completion_verdict,
|
|
1298
|
+
evidence_model: outputWithReceiptPaths.evidence_model,
|
|
1299
|
+
failure_fingerprint: outputWithReceiptPaths.failure_fingerprint,
|
|
1300
|
+
repeated_failure_summary: outputWithReceiptPaths.repeated_failure_summary,
|
|
1301
|
+
summary: outputWithReceiptPaths.summary,
|
|
1302
|
+
...(outputWithReceiptPaths.repro_evidence ? { repro_evidence: outputWithReceiptPaths.repro_evidence } : {}),
|
|
1303
|
+
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
474
1304
|
receipts,
|
|
475
1305
|
};
|
|
476
1306
|
writeFileSync(path.join(runDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
@@ -478,27 +1308,93 @@ function writeVerifyRunReceipts(projectRoot, output) {
|
|
|
478
1308
|
schema_version: '1',
|
|
479
1309
|
command: 'verify',
|
|
480
1310
|
kind: 'verify_run_summary',
|
|
481
|
-
reason:
|
|
482
|
-
reasons:
|
|
483
|
-
plan_source:
|
|
484
|
-
|
|
485
|
-
|
|
1311
|
+
reason: outputWithReceiptPaths.reason,
|
|
1312
|
+
reasons: outputWithReceiptPaths.reasons,
|
|
1313
|
+
plan_source: outputWithReceiptPaths.plan_source,
|
|
1314
|
+
verification_plan_id: outputWithReceiptPaths.verification_plan_id,
|
|
1315
|
+
status: outputWithReceiptPaths.status,
|
|
1316
|
+
completion_verdict: outputWithReceiptPaths.completion_verdict,
|
|
1317
|
+
evidence_model: outputWithReceiptPaths.evidence_model,
|
|
1318
|
+
failure_fingerprint: outputWithReceiptPaths.failure_fingerprint,
|
|
1319
|
+
repeated_failure_summary: outputWithReceiptPaths.repeated_failure_summary,
|
|
1320
|
+
summary: outputWithReceiptPaths.summary,
|
|
1321
|
+
...(outputWithReceiptPaths.repro_evidence ? { repro_evidence: outputWithReceiptPaths.repro_evidence } : {}),
|
|
1322
|
+
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
486
1323
|
run_dir: toPosixPath(VERIFY_RUN_DIR),
|
|
487
|
-
manifest_path: toPosixPath(
|
|
1324
|
+
manifest_path: toPosixPath(VERIFY_MANIFEST_PATH),
|
|
488
1325
|
};
|
|
489
1326
|
writeFileSync(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), `${JSON.stringify(latest, null, 2)}\n`, 'utf8');
|
|
1327
|
+
return outputWithReceiptPaths;
|
|
490
1328
|
}
|
|
491
|
-
async function createVerifyOutput(input, planSource, projectRoot, lang) {
|
|
1329
|
+
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = []) {
|
|
492
1330
|
const contract = readCommandContract(projectRoot);
|
|
493
1331
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
1332
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
494
1333
|
const scheduledIntents = new Set(report.schedule.entries.map((entry) => entry.intent));
|
|
495
1334
|
const scheduledTestTargets = testTargetsByScheduledIntent(report);
|
|
1335
|
+
const sourceAnchorRisks = await readLocalSourceAnchorVerdictRisks(projectRoot, report.files);
|
|
1336
|
+
const scopeDiffRisks = createScopeDiffRisks(input.classificationReport);
|
|
1337
|
+
const validationRatchetRisks = createValidationRatchetRisks(input.classificationReport, projectRoot);
|
|
1338
|
+
const validationRatchetVerdictEffects = countValidationRatchetVerdictEffects(validationRatchetRisks);
|
|
1339
|
+
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, { verificationPlanId });
|
|
1340
|
+
const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
|
|
1341
|
+
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
496
1342
|
const results = [];
|
|
497
1343
|
for (const entry of report.schedule.entries) {
|
|
498
|
-
results.push(await runVerificationIntent(entry.intent, lang, scheduledTestTargets.get(entry.intent) ?? []));
|
|
1344
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
499
1345
|
}
|
|
500
1346
|
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
501
1347
|
const summary = summarizeResults(results);
|
|
1348
|
+
const status = getVerificationStatus(summary);
|
|
1349
|
+
const previousVerifyLatest = readPreviousVerifyLatestSummary(projectRoot);
|
|
1350
|
+
const failureFingerprint = createFailureFingerprintForVerify({
|
|
1351
|
+
verificationPlanId,
|
|
1352
|
+
report,
|
|
1353
|
+
results,
|
|
1354
|
+
riskCodes: riskCodesForFailureFingerprint({
|
|
1355
|
+
sourceAnchorRisks,
|
|
1356
|
+
scopeDiffRisks,
|
|
1357
|
+
validationRatchetRisks,
|
|
1358
|
+
reproEvidenceRisks,
|
|
1359
|
+
externalEvidenceRisks,
|
|
1360
|
+
results,
|
|
1361
|
+
}),
|
|
1362
|
+
});
|
|
1363
|
+
const repeatedFailureRisks = createRepeatedFailureRisks({
|
|
1364
|
+
previousFailureFingerprint: previousVerifyLatest?.failure_fingerprint ?? null,
|
|
1365
|
+
previousStatus: previousVerifyLatest?.status ?? null,
|
|
1366
|
+
currentFailureFingerprint: failureFingerprint,
|
|
1367
|
+
currentStatus: status,
|
|
1368
|
+
});
|
|
1369
|
+
const completionVerdict = createCompletionVerdictForResults({
|
|
1370
|
+
report,
|
|
1371
|
+
verificationPlanId,
|
|
1372
|
+
summary,
|
|
1373
|
+
results,
|
|
1374
|
+
sourceAnchorRiskCount: sourceAnchorRisks.length,
|
|
1375
|
+
scopeDiffRiskCount: scopeDiffRisks.length,
|
|
1376
|
+
repeatedFailureRisks,
|
|
1377
|
+
validationRatchetRiskCount: validationRatchetRisks.length,
|
|
1378
|
+
validationRatchetContradictionCount: validationRatchetVerdictEffects.contradicted,
|
|
1379
|
+
reproEvidenceRiskCount: reproEvidenceRisks.length,
|
|
1380
|
+
reproEvidenceContradictionCount: reproEvidenceVerdictEffects.contradicted,
|
|
1381
|
+
reproEvidenceUnverifiedCount: reproEvidenceVerdictEffects.unverified,
|
|
1382
|
+
externalEvidenceRiskCount: externalEvidenceRisks.length,
|
|
1383
|
+
});
|
|
1384
|
+
const evidenceModel = createVerifyEvidenceModel({
|
|
1385
|
+
report,
|
|
1386
|
+
results,
|
|
1387
|
+
verificationPlanId,
|
|
1388
|
+
verdict: completionVerdict,
|
|
1389
|
+
sourceAnchorRisks,
|
|
1390
|
+
scopeDiffRisks,
|
|
1391
|
+
repeatedFailureRisks,
|
|
1392
|
+
validationRatchetRisks,
|
|
1393
|
+
reproEvidence,
|
|
1394
|
+
reproEvidenceRisks,
|
|
1395
|
+
externalChecks,
|
|
1396
|
+
externalEvidenceRisks,
|
|
1397
|
+
});
|
|
502
1398
|
const output = {
|
|
503
1399
|
schema_version: VERIFY_SCHEMA_VERSION,
|
|
504
1400
|
command: 'verify',
|
|
@@ -506,16 +1402,25 @@ async function createVerifyOutput(input, planSource, projectRoot, lang) {
|
|
|
506
1402
|
reason: input.reasons.join(', '),
|
|
507
1403
|
reasons: input.reasons,
|
|
508
1404
|
plan_source: planSource,
|
|
509
|
-
|
|
1405
|
+
verification_plan_id: verificationPlanId,
|
|
1406
|
+
status,
|
|
1407
|
+
completion_verdict: completionVerdict,
|
|
1408
|
+
evidence_model: evidenceModel,
|
|
1409
|
+
failure_fingerprint: failureFingerprint,
|
|
1410
|
+
repeated_failure_summary: null,
|
|
510
1411
|
summary,
|
|
1412
|
+
...(reproEvidence ? { repro_evidence: reproEvidence } : {}),
|
|
1413
|
+
...(externalChecks.length > 0 ? { external_checks: externalChecks } : {}),
|
|
1414
|
+
run_dir: toPosixPath(VERIFY_RUN_DIR),
|
|
1415
|
+
manifest_path: toPosixPath(VERIFY_MANIFEST_PATH),
|
|
511
1416
|
results,
|
|
512
1417
|
};
|
|
513
|
-
writeVerifyRunReceipts(projectRoot, output);
|
|
514
|
-
return output;
|
|
1418
|
+
return writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, validationRatchetRisks, reproEvidence, externalChecks);
|
|
515
1419
|
}
|
|
516
1420
|
async function createPlanOnlyOutput(input, projectRoot) {
|
|
517
1421
|
const contract = readCommandContract(projectRoot);
|
|
518
1422
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
1423
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
519
1424
|
const localSurfaceReadModels = await readLocalPathSurfaces(projectRoot, report.files);
|
|
520
1425
|
const [firstEntry] = report.schedule.entries;
|
|
521
1426
|
const requirements = report.requirements.map((requirement) => {
|
|
@@ -525,7 +1430,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
525
1430
|
return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
|
|
526
1431
|
});
|
|
527
1432
|
if (!firstEntry) {
|
|
528
|
-
return { ...report, requirements };
|
|
1433
|
+
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
529
1434
|
}
|
|
530
1435
|
const firstGraph = await readLocalCommandEffectGraph(projectRoot, firstEntry.intent);
|
|
531
1436
|
const graphsByIntent = new Map([[firstEntry.intent, firstGraph]]);
|
|
@@ -538,6 +1443,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
538
1443
|
}
|
|
539
1444
|
return {
|
|
540
1445
|
...report,
|
|
1446
|
+
verification_plan_id: verificationPlanId,
|
|
541
1447
|
requirements,
|
|
542
1448
|
schedule: {
|
|
543
1449
|
...report.schedule,
|
|
@@ -555,6 +1461,7 @@ function renderVerifyOutput(output, lang) {
|
|
|
555
1461
|
`${t(lang, 'verify.label.reason')}: ${output.reason}`,
|
|
556
1462
|
`${t(lang, 'verify.label.planSource')}: ${output.plan_source ?? t(lang, 'value.none')}`,
|
|
557
1463
|
`${t(lang, 'verify.label.status')}: ${output.status}`,
|
|
1464
|
+
`completion verdict: ${output.completion_verdict.status} (${output.completion_verdict.primary_reason})`,
|
|
558
1465
|
`matched: ${output.summary.matched}`,
|
|
559
1466
|
`ran: ${output.summary.ran}`,
|
|
560
1467
|
`passed: ${output.summary.passed}`,
|
|
@@ -579,17 +1486,28 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
579
1486
|
if (parsed.error) {
|
|
580
1487
|
const message = parsed.error === 'missing_reason_value'
|
|
581
1488
|
? t(lang, 'cli.error.missingValue', { option: '--reason' })
|
|
582
|
-
: parsed.error === '
|
|
583
|
-
? t(lang, 'cli.error.missingValue', { option: '--from-
|
|
584
|
-
: parsed.error === '
|
|
585
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
586
|
-
: parsed.error
|
|
587
|
-
? t(lang, 'cli.error.
|
|
588
|
-
:
|
|
1489
|
+
: parsed.error === 'missing_from_classification_value'
|
|
1490
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-classification' })
|
|
1491
|
+
: parsed.error === 'missing_from_plan_value'
|
|
1492
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-plan' })
|
|
1493
|
+
: parsed.error === 'missing_write_plan_value'
|
|
1494
|
+
? t(lang, 'cli.error.missingValue', { option: '--write-plan' })
|
|
1495
|
+
: parsed.error === 'missing_repro_evidence_value'
|
|
1496
|
+
? t(lang, 'cli.error.missingValue', { option: '--repro-evidence' })
|
|
1497
|
+
: parsed.error === 'missing_external_evidence_value'
|
|
1498
|
+
? t(lang, 'cli.error.missingValue', { option: '--external-evidence' })
|
|
1499
|
+
: parsed.error.startsWith('unexpected:')
|
|
1500
|
+
? t(lang, 'cli.error.unexpectedArgument', { argument: parsed.error.slice('unexpected:'.length) })
|
|
1501
|
+
: t(lang, 'cli.error.unknownOption', { option: parsed.error });
|
|
589
1502
|
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
590
1503
|
return 1;
|
|
591
1504
|
}
|
|
592
|
-
const selectedInputCount = [
|
|
1505
|
+
const selectedInputCount = [
|
|
1506
|
+
parsed.reason,
|
|
1507
|
+
parsed.fromClassification,
|
|
1508
|
+
parsed.fromPlan,
|
|
1509
|
+
parsed.changed ? 'changed' : undefined,
|
|
1510
|
+
].filter(Boolean).length;
|
|
593
1511
|
if (selectedInputCount > 1) {
|
|
594
1512
|
printUsageError(reporter, t(lang, 'verify.error.conflictingInputs'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
595
1513
|
return 1;
|
|
@@ -606,17 +1524,27 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
606
1524
|
printUsageError(reporter, t(lang, 'verify.error.planOnlyJson'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
607
1525
|
return 1;
|
|
608
1526
|
}
|
|
1527
|
+
if (parsed.planOnly && parsed.reproEvidence) {
|
|
1528
|
+
printUsageError(reporter, t(lang, 'verify.error.reproEvidenceRequiresRun'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
1529
|
+
return 1;
|
|
1530
|
+
}
|
|
1531
|
+
if (parsed.planOnly && parsed.externalEvidence) {
|
|
1532
|
+
printUsageError(reporter, t(lang, 'verify.error.externalEvidenceRequiresRun'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
1533
|
+
return 1;
|
|
1534
|
+
}
|
|
609
1535
|
const projectRoot = resolveMustflowRoot();
|
|
610
1536
|
let input;
|
|
611
1537
|
let changedPlan = null;
|
|
1538
|
+
let reproEvidence = null;
|
|
1539
|
+
let externalChecks = [];
|
|
612
1540
|
try {
|
|
613
1541
|
if (parsed.changed) {
|
|
614
1542
|
const changedInput = createInputFromChanged(projectRoot);
|
|
615
1543
|
input = changedInput.input;
|
|
616
1544
|
changedPlan = changedInput.plan;
|
|
617
1545
|
}
|
|
618
|
-
else if (parsed.fromPlan) {
|
|
619
|
-
input = readInputFromPlan(projectRoot, parsed.fromPlan);
|
|
1546
|
+
else if (parsed.fromClassification || parsed.fromPlan) {
|
|
1547
|
+
input = readInputFromPlan(projectRoot, (parsed.fromClassification ?? parsed.fromPlan));
|
|
620
1548
|
}
|
|
621
1549
|
else {
|
|
622
1550
|
input = {
|
|
@@ -627,17 +1555,32 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
627
1555
|
if (parsed.writePlan && changedPlan) {
|
|
628
1556
|
writeChangedPlan(projectRoot, parsed.writePlan, changedPlan);
|
|
629
1557
|
}
|
|
1558
|
+
if (parsed.reproEvidence) {
|
|
1559
|
+
reproEvidence = readReproEvidenceFile(projectRoot, parsed.reproEvidence);
|
|
1560
|
+
}
|
|
1561
|
+
if (parsed.externalEvidence) {
|
|
1562
|
+
externalChecks = readExternalEvidenceFile(projectRoot, parsed.externalEvidence);
|
|
1563
|
+
}
|
|
630
1564
|
}
|
|
631
1565
|
catch (error) {
|
|
632
1566
|
const code = error instanceof Error ? error.message : 'invalid_plan_file';
|
|
633
|
-
|
|
1567
|
+
const message = code === 'invalid_repro_evidence_file'
|
|
1568
|
+
? t(lang, 'verify.error.invalid_repro_evidence_file')
|
|
1569
|
+
: code === 'unsupported_repro_evidence_source'
|
|
1570
|
+
? t(lang, 'verify.error.unsupported_repro_evidence_source')
|
|
1571
|
+
: code === 'invalid_external_evidence_file'
|
|
1572
|
+
? t(lang, 'verify.error.invalid_external_evidence_file')
|
|
1573
|
+
: code === 'unsupported_external_evidence_source'
|
|
1574
|
+
? t(lang, 'verify.error.unsupported_external_evidence_source')
|
|
1575
|
+
: t(lang, planErrorMessageKey(code));
|
|
1576
|
+
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
634
1577
|
return 1;
|
|
635
1578
|
}
|
|
636
1579
|
if (parsed.planOnly) {
|
|
637
1580
|
reporter.stdout(JSON.stringify(await createPlanOnlyOutput(input, projectRoot), null, 2));
|
|
638
1581
|
return 0;
|
|
639
1582
|
}
|
|
640
|
-
const output = await createVerifyOutput(input, parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang);
|
|
1583
|
+
const output = await createVerifyOutput(input, parsed.fromClassification ?? parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang, reproEvidence, externalChecks);
|
|
641
1584
|
if (parsed.json) {
|
|
642
1585
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
643
1586
|
}
|