mustflow 2.22.5 → 2.22.12
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 +8 -0
- package/dist/cli/commands/check.js +2 -2
- package/dist/cli/commands/classify.js +2 -0
- package/dist/cli/commands/dashboard.js +9 -69
- package/dist/cli/commands/help.js +1 -3
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +14 -1
- package/dist/cli/commands/verify/evidence-input.js +269 -0
- package/dist/cli/commands/verify/input.js +212 -0
- package/dist/cli/commands/verify/state-paths.js +33 -0
- package/dist/cli/commands/verify.js +29 -511
- package/dist/cli/i18n/en.js +3 -0
- package/dist/cli/i18n/es.js +3 -0
- package/dist/cli/i18n/fr.js +3 -0
- package/dist/cli/i18n/hi.js +3 -0
- package/dist/cli/i18n/ko.js +3 -0
- package/dist/cli/i18n/zh.js +3 -0
- package/dist/cli/lib/dashboard-export.js +2 -0
- package/dist/cli/lib/dashboard-mutations.js +79 -0
- package/dist/cli/lib/doc-review-ledger.js +1 -3
- package/dist/cli/lib/local-index/command-effect-index.js +25 -0
- package/dist/cli/lib/local-index/hashing.js +7 -0
- package/dist/cli/lib/local-index/index.js +127 -826
- package/dist/cli/lib/local-index/source-index.js +137 -0
- package/dist/cli/lib/local-index/verification-evidence.js +451 -0
- package/dist/cli/lib/local-index/workflow-documents.js +204 -0
- package/dist/cli/lib/manifest-lock.js +1 -3
- package/dist/cli/lib/repo-map-frontmatter.js +53 -0
- package/dist/cli/lib/repo-map.js +10 -57
- package/dist/cli/lib/run-root-trust.js +27 -0
- package/dist/cli/lib/validation/index.js +6 -2
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- package/dist/core/check-issues.js +11 -7
- package/dist/core/command-contract-validation.js +22 -20
- package/dist/core/contract-lint.js +6 -2
- package/dist/core/correlation-id.js +16 -0
- package/dist/core/run-receipt.js +1 -0
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/change-verification-report.schema.json +4 -0
- package/schemas/classify-report.schema.json +4 -0
- package/schemas/dashboard-export.schema.json +4 -0
- package/schemas/latest-run-pointer.schema.json +4 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/verify-report.schema.json +4 -0
- package/schemas/verify-run-manifest.schema.json +4 -0
- package/templates/default/i18n.toml +3 -3
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
- package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
- package/templates/default/locales/ko/.mustflow/context/INDEX.md +12 -12
- package/templates/default/locales/ko/.mustflow/context/PROJECT.md +6 -6
- package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +70 -70
- package/templates/default/locales/ko/AGENTS.md +14 -14
- package/templates/default/manifest.toml +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { createClassifyOutput } from './classify.js';
|
|
4
2
|
import { runRun } from './run.js';
|
|
5
3
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
4
|
+
import { createCorrelationId } from '../../core/correlation-id.js';
|
|
6
5
|
import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
7
6
|
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
|
-
import { createStateRunId } from '../../core/atomic-state-write.js';
|
|
9
7
|
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
10
8
|
import { createRepeatedFailureRisks, createVerificationFailureFingerprint, updateRepeatedFailureState, } from '../../core/repeated-failure.js';
|
|
11
9
|
import { countReproEvidenceVerdictEffects, createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
@@ -15,13 +13,15 @@ import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } f
|
|
|
15
13
|
import { finishRunWriteBatchTracking, startRunWriteBatchTracking, } from '../../core/run-write-drift.js';
|
|
16
14
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
17
15
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
|
|
16
|
+
import { createInputFromChanged, createSyntheticClassificationReport, planErrorMessageKey, readInputFromClassificationReport, resolveVerifyInputPath, writeChangedPlan, } from './verify/input.js';
|
|
17
|
+
import { readExternalEvidenceFile, readReproEvidenceFile } from './verify/evidence-input.js';
|
|
18
|
+
import { createVerifyIntentReceiptPath, createVerifyRunStatePaths, resolveLatestVerifyRunReceiptPath, } from './verify/state-paths.js';
|
|
18
19
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
19
20
|
import { t } from '../lib/i18n.js';
|
|
20
21
|
import { readLocalCommandEffectGraphs, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
21
22
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
23
|
+
export { planErrorMessageKey, readInputFromClassificationReport } from './verify/input.js';
|
|
22
24
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
23
|
-
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
24
|
-
const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
|
|
25
25
|
function createBufferedOutput() {
|
|
26
26
|
const stdout = [];
|
|
27
27
|
const stderr = [];
|
|
@@ -77,493 +77,6 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
77
77
|
function uniqueStrings(values) {
|
|
78
78
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
79
79
|
}
|
|
80
|
-
function toPosixPath(value) {
|
|
81
|
-
return value.split(path.sep).join('/');
|
|
82
|
-
}
|
|
83
|
-
function createVerifyRunStatePaths(projectRoot) {
|
|
84
|
-
const runDir = toPosixPath(path.join(RUN_STATE_DIR, createStateRunId('verify')));
|
|
85
|
-
const manifestPath = toPosixPath(path.join(runDir, 'manifest.json'));
|
|
86
|
-
const absoluteRunDir = path.join(projectRoot, runDir);
|
|
87
|
-
return {
|
|
88
|
-
runDir,
|
|
89
|
-
manifestPath,
|
|
90
|
-
absoluteRunDir,
|
|
91
|
-
absoluteIntentDir: path.join(absoluteRunDir, 'intents'),
|
|
92
|
-
absoluteManifestPath: path.join(projectRoot, manifestPath),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function sanitizeIntentFilePart(value) {
|
|
96
|
-
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
97
|
-
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
98
|
-
}
|
|
99
|
-
function readStringArray(value) {
|
|
100
|
-
if (!Array.isArray(value)) {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
return value.filter((item) => typeof item === 'string');
|
|
104
|
-
}
|
|
105
|
-
function isStringArray(value) {
|
|
106
|
-
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
107
|
-
}
|
|
108
|
-
function isPlainRecord(value) {
|
|
109
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
110
|
-
}
|
|
111
|
-
function isUpdatePolicy(value) {
|
|
112
|
-
return value === 'update' || value === 'update_or_mark_stale' || value === 'not_applicable';
|
|
113
|
-
}
|
|
114
|
-
function isUpdatePolicyArray(value) {
|
|
115
|
-
return Array.isArray(value) && value.every((item) => isUpdatePolicy(item));
|
|
116
|
-
}
|
|
117
|
-
function readPlanRoot(value) {
|
|
118
|
-
if (!isPlainRecord(value)) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
const root = value.mustflow_root;
|
|
122
|
-
return typeof root === 'string' && root.length > 0 ? root : null;
|
|
123
|
-
}
|
|
124
|
-
function readStrictClassificationSummary(value) {
|
|
125
|
-
if (!isPlainRecord(value)) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
if (!Number.isInteger(value.fileCount) ||
|
|
129
|
-
Number(value.fileCount) < 0 ||
|
|
130
|
-
!Number.isInteger(value.publicSurfaceCount) ||
|
|
131
|
-
Number(value.publicSurfaceCount) < 0 ||
|
|
132
|
-
!isStringArray(value.changeKinds) ||
|
|
133
|
-
!isStringArray(value.validationReasons) ||
|
|
134
|
-
!isUpdatePolicyArray(value.updatePolicies) ||
|
|
135
|
-
!isStringArray(value.driftChecks) ||
|
|
136
|
-
!isStringArray(value.affectedContracts)) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
fileCount: Number(value.fileCount),
|
|
141
|
-
publicSurfaceCount: Number(value.publicSurfaceCount),
|
|
142
|
-
changeKinds: [...value.changeKinds],
|
|
143
|
-
validationReasons: uniqueStrings(value.validationReasons),
|
|
144
|
-
updatePolicies: [...value.updatePolicies],
|
|
145
|
-
driftChecks: [...value.driftChecks],
|
|
146
|
-
affectedContracts: [...value.affectedContracts],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
function readStrictClassification(value) {
|
|
150
|
-
if (!isPlainRecord(value) || typeof value.path !== 'string' || !isStringArray(value.changeKinds)) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
const surface = value.surface;
|
|
154
|
-
if (!isPlainRecord(surface)) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
if (typeof surface.kind !== 'string' ||
|
|
158
|
-
typeof surface.category !== 'string' ||
|
|
159
|
-
typeof surface.isPublicSurface !== 'boolean' ||
|
|
160
|
-
!isStringArray(surface.validationReasons) ||
|
|
161
|
-
!isStringArray(surface.affectedContracts) ||
|
|
162
|
-
!isUpdatePolicy(surface.updatePolicy) ||
|
|
163
|
-
!isStringArray(surface.driftChecks)) {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
path: value.path,
|
|
168
|
-
changeKinds: [...value.changeKinds],
|
|
169
|
-
surface: {
|
|
170
|
-
kind: surface.kind,
|
|
171
|
-
category: surface.category,
|
|
172
|
-
isPublicSurface: surface.isPublicSurface,
|
|
173
|
-
validationReasons: [...surface.validationReasons],
|
|
174
|
-
affectedContracts: [...surface.affectedContracts],
|
|
175
|
-
updatePolicy: surface.updatePolicy,
|
|
176
|
-
driftChecks: [...surface.driftChecks],
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
function readStrictClassifications(value) {
|
|
181
|
-
if (!Array.isArray(value)) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
const classifications = value.map(readStrictClassification);
|
|
185
|
-
return classifications.every((classification) => classification !== null)
|
|
186
|
-
? classifications
|
|
187
|
-
: null;
|
|
188
|
-
}
|
|
189
|
-
function readStrictClassifyPlan(projectRoot, plan) {
|
|
190
|
-
if (!isPlainRecord(plan)) {
|
|
191
|
-
throw new Error('unsupported_plan_source');
|
|
192
|
-
}
|
|
193
|
-
if (plan.schema_version !== '1' || plan.command !== 'classify') {
|
|
194
|
-
throw new Error('unsupported_plan_source');
|
|
195
|
-
}
|
|
196
|
-
if (readPlanRoot(plan) !== projectRoot) {
|
|
197
|
-
throw new Error('plan_root_mismatch');
|
|
198
|
-
}
|
|
199
|
-
const source = plan.source;
|
|
200
|
-
const files = plan.files;
|
|
201
|
-
const classifications = readStrictClassifications(plan.classifications);
|
|
202
|
-
const summary = readStrictClassificationSummary(plan.summary);
|
|
203
|
-
if ((source !== 'changed' && source !== 'paths') || !isStringArray(files) || !classifications || !summary) {
|
|
204
|
-
throw new Error('invalid_plan_file');
|
|
205
|
-
}
|
|
206
|
-
if (summary.validationReasons.length === 0) {
|
|
207
|
-
throw new Error('missing_plan_reasons');
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
source,
|
|
211
|
-
files: [...files],
|
|
212
|
-
classifications,
|
|
213
|
-
summary,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
function emptyClassificationSummary(validationReasons) {
|
|
217
|
-
return {
|
|
218
|
-
fileCount: 0,
|
|
219
|
-
publicSurfaceCount: 0,
|
|
220
|
-
changeKinds: [],
|
|
221
|
-
validationReasons,
|
|
222
|
-
updatePolicies: [],
|
|
223
|
-
driftChecks: [],
|
|
224
|
-
affectedContracts: [],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function createSyntheticClassificationReport(reasons, source = 'paths', files = []) {
|
|
228
|
-
return {
|
|
229
|
-
source,
|
|
230
|
-
files,
|
|
231
|
-
classifications: [],
|
|
232
|
-
summary: emptyClassificationSummary(reasons),
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function resolvePlanPath(projectRoot, inputPath) {
|
|
236
|
-
const resolved = path.resolve(projectRoot, inputPath);
|
|
237
|
-
const relative = path.relative(projectRoot, resolved);
|
|
238
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
239
|
-
throw new Error('plan_path_outside_root');
|
|
240
|
-
}
|
|
241
|
-
return resolved;
|
|
242
|
-
}
|
|
243
|
-
function readJsonInputFile(projectRoot, inputPath, invalidCode) {
|
|
244
|
-
const inputFilePath = resolvePlanPath(projectRoot, inputPath);
|
|
245
|
-
let content;
|
|
246
|
-
try {
|
|
247
|
-
content = readUtf8FileInsideWithoutSymlinks(projectRoot, inputFilePath);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
if (error instanceof Error && error.message.startsWith('Path must not contain symlinks:')) {
|
|
251
|
-
throw new Error('input_path_contains_symlink');
|
|
252
|
-
}
|
|
253
|
-
throw new Error(invalidCode);
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
return JSON.parse(content);
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
throw new Error(invalidCode);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
export function readInputFromClassificationReport(projectRoot, inputPath) {
|
|
263
|
-
const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_plan_file');
|
|
264
|
-
const classificationReport = readStrictClassifyPlan(projectRoot, parsed);
|
|
265
|
-
return {
|
|
266
|
-
reasons: classificationReport.summary.validationReasons,
|
|
267
|
-
classificationReport,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function isExternalEvidenceStatus(value) {
|
|
271
|
-
return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
|
|
272
|
-
}
|
|
273
|
-
function isLegacyReproEvidenceStatus(value) {
|
|
274
|
-
return value === 'present' || value === 'unavailable' || value === 'missing';
|
|
275
|
-
}
|
|
276
|
-
function isReproBeforeFixStatus(value) {
|
|
277
|
-
return value === 'reproduced' || value === 'unavailable' || value === 'missing';
|
|
278
|
-
}
|
|
279
|
-
function isReproBeforeFixOutcome(value) {
|
|
280
|
-
return value === 'failed_as_expected' || value === 'failed_differently' || value === 'passed_unexpectedly' || value === null;
|
|
281
|
-
}
|
|
282
|
-
function isReproAfterFixStatus(value) {
|
|
283
|
-
return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
|
|
284
|
-
}
|
|
285
|
-
function isReproAfterFixOutcome(value) {
|
|
286
|
-
return value === 'passed_expected_behavior' || value === 'failed_same_route' || value === 'failed_differently' || value === null;
|
|
287
|
-
}
|
|
288
|
-
function isReproRegressionGuardStatus(value) {
|
|
289
|
-
return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
|
|
290
|
-
}
|
|
291
|
-
function isReproRouteKind(value) {
|
|
292
|
-
return (value === 'test' ||
|
|
293
|
-
value === 'cli' ||
|
|
294
|
-
value === 'browser' ||
|
|
295
|
-
value === 'api' ||
|
|
296
|
-
value === 'manual' ||
|
|
297
|
-
value === 'unknown' ||
|
|
298
|
-
value === null);
|
|
299
|
-
}
|
|
300
|
-
function readOptionalString(value) {
|
|
301
|
-
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
302
|
-
}
|
|
303
|
-
function readRouteStep(value, index) {
|
|
304
|
-
if (!isPlainRecord(value)) {
|
|
305
|
-
return {
|
|
306
|
-
ordinal: index + 1,
|
|
307
|
-
action: null,
|
|
308
|
-
target: null,
|
|
309
|
-
input_digest: null,
|
|
310
|
-
observation_digest: null,
|
|
311
|
-
summary: null,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
const ordinal = typeof value.ordinal === 'number' && Number.isInteger(value.ordinal) && value.ordinal > 0 ? value.ordinal : index + 1;
|
|
315
|
-
return {
|
|
316
|
-
ordinal,
|
|
317
|
-
action: readOptionalString(value.action),
|
|
318
|
-
target: readOptionalString(value.target),
|
|
319
|
-
input_digest: readOptionalString(value.input_digest),
|
|
320
|
-
observation_digest: readOptionalString(value.observation_digest),
|
|
321
|
-
summary: readOptionalString(value.summary),
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
function readReproductionRoute(value) {
|
|
325
|
-
if (!isPlainRecord(value)) {
|
|
326
|
-
return {
|
|
327
|
-
route_id: null,
|
|
328
|
-
route_kind: null,
|
|
329
|
-
route_digest: null,
|
|
330
|
-
failure_oracle_hash: null,
|
|
331
|
-
steps: [],
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
const routeKind = value.route_kind ?? null;
|
|
335
|
-
if (!isReproRouteKind(routeKind)) {
|
|
336
|
-
throw new Error('invalid_repro_evidence_file');
|
|
337
|
-
}
|
|
338
|
-
const rawSteps = Array.isArray(value.steps) ? value.steps : [];
|
|
339
|
-
return {
|
|
340
|
-
route_id: readOptionalString(value.route_id),
|
|
341
|
-
route_kind: routeKind,
|
|
342
|
-
route_digest: readOptionalString(value.route_digest),
|
|
343
|
-
failure_oracle_hash: readOptionalString(value.failure_oracle_hash),
|
|
344
|
-
steps: rawSteps.map((step, index) => readRouteStep(step, index)),
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
function readLegacyReproEvidenceItem(value) {
|
|
348
|
-
if (!isPlainRecord(value)) {
|
|
349
|
-
return {
|
|
350
|
-
status: 'missing',
|
|
351
|
-
summary: null,
|
|
352
|
-
reason: null,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
if (!isLegacyReproEvidenceStatus(value.status)) {
|
|
356
|
-
throw new Error('invalid_repro_evidence_file');
|
|
357
|
-
}
|
|
358
|
-
return {
|
|
359
|
-
status: value.status,
|
|
360
|
-
summary: readOptionalString(value.summary),
|
|
361
|
-
reason: readOptionalString(value.reason),
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function legacyBeforeFixEvidence(value) {
|
|
365
|
-
const item = readLegacyReproEvidenceItem(value);
|
|
366
|
-
return {
|
|
367
|
-
status: item.status === 'present' ? 'reproduced' : item.status,
|
|
368
|
-
outcome: item.status === 'present' ? 'failed_as_expected' : null,
|
|
369
|
-
receipt_path: null,
|
|
370
|
-
receipt_sha256: null,
|
|
371
|
-
verification_plan_id: null,
|
|
372
|
-
summary: item.summary,
|
|
373
|
-
reason: item.reason,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
function legacyAfterFixEvidence(value) {
|
|
377
|
-
const item = readLegacyReproEvidenceItem(value);
|
|
378
|
-
return {
|
|
379
|
-
status: item.status === 'present' ? 'passed' : item.status,
|
|
380
|
-
outcome: item.status === 'present' ? 'passed_expected_behavior' : null,
|
|
381
|
-
same_route_as: null,
|
|
382
|
-
receipt_path: null,
|
|
383
|
-
receipt_sha256: null,
|
|
384
|
-
verification_plan_id: null,
|
|
385
|
-
summary: item.summary,
|
|
386
|
-
reason: item.reason,
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function legacyRegressionGuardEvidence(value) {
|
|
390
|
-
const item = readLegacyReproEvidenceItem(value);
|
|
391
|
-
return {
|
|
392
|
-
status: item.status === 'present' ? 'passed' : item.status,
|
|
393
|
-
intent: null,
|
|
394
|
-
test_path: null,
|
|
395
|
-
receipt_path: null,
|
|
396
|
-
receipt_sha256: null,
|
|
397
|
-
verification_plan_id: null,
|
|
398
|
-
summary: item.summary,
|
|
399
|
-
reason: item.reason,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
function readBeforeFixEvidence(value) {
|
|
403
|
-
if (!isPlainRecord(value)) {
|
|
404
|
-
return {
|
|
405
|
-
status: 'missing',
|
|
406
|
-
outcome: null,
|
|
407
|
-
receipt_path: null,
|
|
408
|
-
receipt_sha256: null,
|
|
409
|
-
verification_plan_id: null,
|
|
410
|
-
summary: null,
|
|
411
|
-
reason: null,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
const outcome = value.outcome ?? null;
|
|
415
|
-
if (!isReproBeforeFixStatus(value.status) || !isReproBeforeFixOutcome(outcome)) {
|
|
416
|
-
throw new Error('invalid_repro_evidence_file');
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
status: value.status,
|
|
420
|
-
outcome,
|
|
421
|
-
receipt_path: readOptionalString(value.receipt_path),
|
|
422
|
-
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
423
|
-
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
424
|
-
summary: readOptionalString(value.summary),
|
|
425
|
-
reason: readOptionalString(value.reason),
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
function readAfterFixEvidence(value) {
|
|
429
|
-
if (!isPlainRecord(value)) {
|
|
430
|
-
return {
|
|
431
|
-
status: 'missing',
|
|
432
|
-
outcome: null,
|
|
433
|
-
same_route_as: null,
|
|
434
|
-
receipt_path: null,
|
|
435
|
-
receipt_sha256: null,
|
|
436
|
-
verification_plan_id: null,
|
|
437
|
-
summary: null,
|
|
438
|
-
reason: null,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
const outcome = value.outcome ?? null;
|
|
442
|
-
if (!isReproAfterFixStatus(value.status) || !isReproAfterFixOutcome(outcome)) {
|
|
443
|
-
throw new Error('invalid_repro_evidence_file');
|
|
444
|
-
}
|
|
445
|
-
return {
|
|
446
|
-
status: value.status,
|
|
447
|
-
outcome,
|
|
448
|
-
same_route_as: readOptionalString(value.same_route_as),
|
|
449
|
-
receipt_path: readOptionalString(value.receipt_path),
|
|
450
|
-
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
451
|
-
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
452
|
-
summary: readOptionalString(value.summary),
|
|
453
|
-
reason: readOptionalString(value.reason),
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
function readRegressionGuardEvidence(value) {
|
|
457
|
-
if (!isPlainRecord(value)) {
|
|
458
|
-
return {
|
|
459
|
-
status: 'missing',
|
|
460
|
-
intent: null,
|
|
461
|
-
test_path: null,
|
|
462
|
-
receipt_path: null,
|
|
463
|
-
receipt_sha256: null,
|
|
464
|
-
verification_plan_id: null,
|
|
465
|
-
summary: null,
|
|
466
|
-
reason: null,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
if (!isReproRegressionGuardStatus(value.status)) {
|
|
470
|
-
throw new Error('invalid_repro_evidence_file');
|
|
471
|
-
}
|
|
472
|
-
return {
|
|
473
|
-
status: value.status,
|
|
474
|
-
intent: readOptionalString(value.intent),
|
|
475
|
-
test_path: readOptionalString(value.test_path),
|
|
476
|
-
receipt_path: readOptionalString(value.receipt_path),
|
|
477
|
-
receipt_sha256: readOptionalString(value.receipt_sha256),
|
|
478
|
-
verification_plan_id: readOptionalString(value.verification_plan_id),
|
|
479
|
-
summary: readOptionalString(value.summary),
|
|
480
|
-
reason: readOptionalString(value.reason),
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function readReproEvidenceFile(projectRoot, inputPath) {
|
|
484
|
-
const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_repro_evidence_file');
|
|
485
|
-
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
|
|
486
|
-
throw new Error('unsupported_repro_evidence_source');
|
|
487
|
-
}
|
|
488
|
-
const regressionGuard = isPlainRecord(parsed.regression_guard) && isReproRegressionGuardStatus(parsed.regression_guard.status)
|
|
489
|
-
? readRegressionGuardEvidence(parsed.regression_guard)
|
|
490
|
-
: legacyRegressionGuardEvidence(parsed.regression_guard);
|
|
491
|
-
return {
|
|
492
|
-
source: 'repro_first_debug',
|
|
493
|
-
authority: 'claim_evidence',
|
|
494
|
-
reported_symptom: readOptionalString(parsed.reported_symptom),
|
|
495
|
-
expected_behavior: readOptionalString(parsed.expected_behavior),
|
|
496
|
-
observed_behavior: readOptionalString(parsed.observed_behavior),
|
|
497
|
-
reproduction_route: readReproductionRoute(parsed.reproduction_route),
|
|
498
|
-
before_fix: isPlainRecord(parsed.before_fix)
|
|
499
|
-
? readBeforeFixEvidence(parsed.before_fix)
|
|
500
|
-
: legacyBeforeFixEvidence(parsed.evidence_before_fix),
|
|
501
|
-
after_fix: isPlainRecord(parsed.after_fix)
|
|
502
|
-
? readAfterFixEvidence(parsed.after_fix)
|
|
503
|
-
: legacyAfterFixEvidence(parsed.evidence_after_fix),
|
|
504
|
-
regression_guard: regressionGuard,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
function readExternalEvidenceFile(projectRoot, inputPath) {
|
|
508
|
-
const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_external_evidence_file');
|
|
509
|
-
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
|
|
510
|
-
throw new Error('unsupported_external_evidence_source');
|
|
511
|
-
}
|
|
512
|
-
if (!Array.isArray(parsed.checks)) {
|
|
513
|
-
throw new Error('invalid_external_evidence_file');
|
|
514
|
-
}
|
|
515
|
-
return parsed.checks.map((check) => {
|
|
516
|
-
if (!isPlainRecord(check) ||
|
|
517
|
-
typeof check.provider !== 'string' ||
|
|
518
|
-
check.provider.length === 0 ||
|
|
519
|
-
typeof check.name !== 'string' ||
|
|
520
|
-
check.name.length === 0 ||
|
|
521
|
-
!isExternalEvidenceStatus(check.status)) {
|
|
522
|
-
throw new Error('invalid_external_evidence_file');
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
source: 'external_ci',
|
|
526
|
-
authority: 'supporting_only',
|
|
527
|
-
provider: check.provider,
|
|
528
|
-
name: check.name,
|
|
529
|
-
status: check.status,
|
|
530
|
-
url: readOptionalString(check.url),
|
|
531
|
-
summary: readOptionalString(check.summary),
|
|
532
|
-
};
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
function createInputFromChanged(projectRoot) {
|
|
536
|
-
const plan = createClassifyOutput(projectRoot, 'changed', []);
|
|
537
|
-
return {
|
|
538
|
-
plan,
|
|
539
|
-
input: {
|
|
540
|
-
reasons: plan.summary.validationReasons,
|
|
541
|
-
classificationReport: plan,
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
function writeChangedPlan(projectRoot, inputPath, plan) {
|
|
546
|
-
const planPath = resolvePlanPath(projectRoot, inputPath);
|
|
547
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot, planPath, plan);
|
|
548
|
-
}
|
|
549
|
-
export function planErrorMessageKey(code) {
|
|
550
|
-
switch (code) {
|
|
551
|
-
case 'plan_path_outside_root':
|
|
552
|
-
return 'verify.error.plan_path_outside_root';
|
|
553
|
-
case 'input_path_contains_symlink':
|
|
554
|
-
return 'verify.error.input_path_contains_symlink';
|
|
555
|
-
case 'missing_plan_reasons':
|
|
556
|
-
return 'verify.error.missing_plan_reasons';
|
|
557
|
-
case 'unsupported_plan_source':
|
|
558
|
-
return 'verify.error.unsupported_plan_source';
|
|
559
|
-
case 'plan_root_mismatch':
|
|
560
|
-
return 'verify.error.plan_root_mismatch';
|
|
561
|
-
case 'git_changed_files_unavailable':
|
|
562
|
-
return 'verify.error.changed_files_unavailable';
|
|
563
|
-
default:
|
|
564
|
-
return 'verify.error.invalid_plan_file';
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
80
|
function skippedResult(candidate) {
|
|
568
81
|
return {
|
|
569
82
|
intent: candidate.intent || null,
|
|
@@ -628,9 +141,10 @@ function testTargetsByScheduledIntent(report) {
|
|
|
628
141
|
candidate.appliedTestTargets.length > 0)
|
|
629
142
|
.map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
|
|
630
143
|
}
|
|
631
|
-
async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = [], additionalDeclaredWritePaths = []) {
|
|
144
|
+
async function runVerificationIntent(intent, lang, verificationPlanId, correlationId, testTargets = [], additionalDeclaredWritePaths = []) {
|
|
632
145
|
const output = createBufferedOutput();
|
|
633
146
|
const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
|
|
147
|
+
correlationId,
|
|
634
148
|
writeLatestReceipt: false,
|
|
635
149
|
writeLatestProfile: false,
|
|
636
150
|
recordPerformanceHistory: false,
|
|
@@ -672,19 +186,19 @@ function entriesForScheduleBatch(entries, batch) {
|
|
|
672
186
|
const batchIntents = new Set(batch.intents);
|
|
673
187
|
return entries.filter((entry) => batchIntents.has(entry.intent));
|
|
674
188
|
}
|
|
675
|
-
async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets) {
|
|
189
|
+
async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets) {
|
|
676
190
|
const results = [];
|
|
677
191
|
for (const entry of entries) {
|
|
678
|
-
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
192
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
679
193
|
}
|
|
680
194
|
return results;
|
|
681
195
|
}
|
|
682
|
-
async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets) {
|
|
196
|
+
async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets) {
|
|
683
197
|
const results = [];
|
|
684
198
|
for (let index = 0; index < entries.length; index += parallelism) {
|
|
685
199
|
const chunk = entries.slice(index, index + parallelism);
|
|
686
200
|
const batchTracker = startRunWriteBatchTracking(projectRoot);
|
|
687
|
-
const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? [])));
|
|
201
|
+
const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? [])));
|
|
688
202
|
const writeDriftByIntent = finishRunWriteBatchTracking(batchTracker, chunk.map((entry) => ({
|
|
689
203
|
intentName: entry.intent,
|
|
690
204
|
declaredPaths: declaredWritePathsForScheduleEntry(entry),
|
|
@@ -732,7 +246,7 @@ function verificationResultFailed(result) {
|
|
|
732
246
|
result.status === 'start_failed' ||
|
|
733
247
|
result.status === 'output_limit_exceeded'));
|
|
734
248
|
}
|
|
735
|
-
async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism) {
|
|
249
|
+
async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, correlationId, scheduledTestTargets, parallelism) {
|
|
736
250
|
const results = [];
|
|
737
251
|
for (let batchIndex = 0; batchIndex < report.schedule.batches.length; batchIndex += 1) {
|
|
738
252
|
const batch = report.schedule.batches[batchIndex];
|
|
@@ -744,11 +258,11 @@ async function runScheduledVerificationIntents(report, projectRoot, lang, verifi
|
|
|
744
258
|
if (entries.length > 1 && entries.every((entry) => entry.parallelEligible)) {
|
|
745
259
|
batchResults =
|
|
746
260
|
parallelism > DEFAULT_VERIFY_PARALLELISM
|
|
747
|
-
? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets)
|
|
748
|
-
: await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
261
|
+
? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets)
|
|
262
|
+
: await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
|
|
749
263
|
}
|
|
750
264
|
else {
|
|
751
|
-
batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
265
|
+
batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
|
|
752
266
|
}
|
|
753
267
|
results.push(...batchResults);
|
|
754
268
|
if (!batchResults.some(verificationResultFailed)) {
|
|
@@ -1036,7 +550,7 @@ function readVerificationFailureFingerprint(value) {
|
|
|
1036
550
|
}
|
|
1037
551
|
function readPreviousVerifyLatestSummary(projectRoot) {
|
|
1038
552
|
try {
|
|
1039
|
-
const parsed = JSON.parse(readUtf8FileInsideWithoutSymlinks(projectRoot,
|
|
553
|
+
const parsed = JSON.parse(readUtf8FileInsideWithoutSymlinks(projectRoot, resolveLatestVerifyRunReceiptPath(projectRoot)));
|
|
1040
554
|
if (parsed.command !== 'verify' ||
|
|
1041
555
|
parsed.kind !== 'verify_run_summary' ||
|
|
1042
556
|
typeof parsed.verification_plan_id !== 'string' ||
|
|
@@ -1116,9 +630,8 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1116
630
|
let receiptSha256 = null;
|
|
1117
631
|
let receipt = result.receipt;
|
|
1118
632
|
if (result.intent && result.receipt) {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
receiptPath = toPosixPath(path.join(statePaths.runDir, 'intents', fileName));
|
|
633
|
+
const receiptStatePath = createVerifyIntentReceiptPath(statePaths, index + 1, result.intent);
|
|
634
|
+
receiptPath = receiptStatePath.receiptPath;
|
|
1122
635
|
receipt = {
|
|
1123
636
|
...result.receipt,
|
|
1124
637
|
verification_plan_id: output.verification_plan_id,
|
|
@@ -1126,7 +639,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1126
639
|
};
|
|
1127
640
|
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
1128
641
|
receiptSha256 = hashTextSha256(receiptContent);
|
|
1129
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot, absoluteReceiptPath, receipt);
|
|
642
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, receiptStatePath.absoluteReceiptPath, receipt);
|
|
1130
643
|
}
|
|
1131
644
|
receipts.push({
|
|
1132
645
|
intent: result.intent,
|
|
@@ -1217,6 +730,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1217
730
|
const manifest = {
|
|
1218
731
|
schema_version: '1',
|
|
1219
732
|
command: 'verify',
|
|
733
|
+
correlation_id: outputWithReceiptPaths.correlation_id,
|
|
1220
734
|
reason: outputWithReceiptPaths.reason,
|
|
1221
735
|
reasons: outputWithReceiptPaths.reasons,
|
|
1222
736
|
plan_source: outputWithReceiptPaths.plan_source,
|
|
@@ -1237,6 +751,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1237
751
|
schema_version: '1',
|
|
1238
752
|
command: 'verify',
|
|
1239
753
|
kind: 'verify_run_summary',
|
|
754
|
+
correlation_id: outputWithReceiptPaths.correlation_id,
|
|
1240
755
|
reason: outputWithReceiptPaths.reason,
|
|
1241
756
|
reasons: outputWithReceiptPaths.reasons,
|
|
1242
757
|
plan_source: outputWithReceiptPaths.plan_source,
|
|
@@ -1253,7 +768,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1253
768
|
run_dir: statePaths.runDir,
|
|
1254
769
|
manifest_path: statePaths.manifestPath,
|
|
1255
770
|
};
|
|
1256
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot,
|
|
771
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, resolveLatestVerifyRunReceiptPath(projectRoot), latest);
|
|
1257
772
|
return outputWithReceiptPaths;
|
|
1258
773
|
}
|
|
1259
774
|
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM, parallelismReport = null) {
|
|
@@ -1269,7 +784,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1269
784
|
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, { verificationPlanId });
|
|
1270
785
|
const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
|
|
1271
786
|
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
1272
|
-
const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism);
|
|
787
|
+
const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, input.correlationId, scheduledTestTargets, parallelism);
|
|
1273
788
|
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
1274
789
|
const summary = summarizeResults(results);
|
|
1275
790
|
const status = getVerificationStatus(summary);
|
|
@@ -1325,6 +840,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1325
840
|
const output = {
|
|
1326
841
|
schema_version: VERIFY_SCHEMA_VERSION,
|
|
1327
842
|
command: 'verify',
|
|
843
|
+
correlation_id: input.correlationId,
|
|
1328
844
|
mustflow_root: projectRoot,
|
|
1329
845
|
reason: input.reasons.join(', '),
|
|
1330
846
|
reasons: input.reasons,
|
|
@@ -1359,16 +875,17 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
1359
875
|
return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
|
|
1360
876
|
});
|
|
1361
877
|
if (!firstEntry) {
|
|
1362
|
-
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
878
|
+
return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
|
|
1363
879
|
}
|
|
1364
880
|
const scheduledIntents = Array.from(new Set(report.schedule.entries.map((entry) => entry.intent)));
|
|
1365
881
|
const graphsByIntent = await readLocalCommandEffectGraphs(projectRoot, scheduledIntents);
|
|
1366
882
|
const firstGraph = graphsByIntent.get(firstEntry.intent);
|
|
1367
883
|
if (!firstGraph) {
|
|
1368
|
-
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
884
|
+
return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
|
|
1369
885
|
}
|
|
1370
886
|
return {
|
|
1371
887
|
...report,
|
|
888
|
+
correlation_id: input.correlationId,
|
|
1372
889
|
verification_plan_id: verificationPlanId,
|
|
1373
890
|
requirements,
|
|
1374
891
|
schedule: {
|
|
@@ -1475,7 +992,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1475
992
|
let externalChecks = [];
|
|
1476
993
|
try {
|
|
1477
994
|
if (parsed.writePlan) {
|
|
1478
|
-
|
|
995
|
+
resolveVerifyInputPath(projectRoot, parsed.writePlan);
|
|
1479
996
|
}
|
|
1480
997
|
if (parsed.changed) {
|
|
1481
998
|
const changedInput = createInputFromChanged(projectRoot);
|
|
@@ -1487,6 +1004,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1487
1004
|
}
|
|
1488
1005
|
else {
|
|
1489
1006
|
input = {
|
|
1007
|
+
correlationId: createCorrelationId('verify'),
|
|
1490
1008
|
reasons: [parsed.reason],
|
|
1491
1009
|
classificationReport: createSyntheticClassificationReport([parsed.reason]),
|
|
1492
1010
|
};
|