mustflow 2.22.5 → 2.22.9
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/classify.js +2 -0
- package/dist/cli/commands/dashboard.js +9 -69
- 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.js +23 -482
- 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/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/run-root-trust.js +27 -0
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- 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/manifest.toml +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createClassifyOutput } from './classify.js';
|
|
4
3
|
import { runRun } from './run.js';
|
|
5
4
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
5
|
+
import { createCorrelationId } from '../../core/correlation-id.js';
|
|
6
6
|
import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
7
7
|
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
8
|
import { createStateRunId } from '../../core/atomic-state-write.js';
|
|
@@ -15,10 +15,13 @@ import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } f
|
|
|
15
15
|
import { finishRunWriteBatchTracking, startRunWriteBatchTracking, } from '../../core/run-write-drift.js';
|
|
16
16
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
17
17
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
|
|
18
|
+
import { createInputFromChanged, createSyntheticClassificationReport, planErrorMessageKey, readInputFromClassificationReport, resolveVerifyInputPath, writeChangedPlan, } from './verify/input.js';
|
|
19
|
+
import { readExternalEvidenceFile, readReproEvidenceFile } from './verify/evidence-input.js';
|
|
18
20
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
19
21
|
import { t } from '../lib/i18n.js';
|
|
20
22
|
import { readLocalCommandEffectGraphs, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
21
23
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
24
|
+
export { planErrorMessageKey, readInputFromClassificationReport } from './verify/input.js';
|
|
22
25
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
23
26
|
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
24
27
|
const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
|
|
@@ -96,474 +99,6 @@ function sanitizeIntentFilePart(value) {
|
|
|
96
99
|
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
97
100
|
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
98
101
|
}
|
|
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
102
|
function skippedResult(candidate) {
|
|
568
103
|
return {
|
|
569
104
|
intent: candidate.intent || null,
|
|
@@ -628,9 +163,10 @@ function testTargetsByScheduledIntent(report) {
|
|
|
628
163
|
candidate.appliedTestTargets.length > 0)
|
|
629
164
|
.map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
|
|
630
165
|
}
|
|
631
|
-
async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = [], additionalDeclaredWritePaths = []) {
|
|
166
|
+
async function runVerificationIntent(intent, lang, verificationPlanId, correlationId, testTargets = [], additionalDeclaredWritePaths = []) {
|
|
632
167
|
const output = createBufferedOutput();
|
|
633
168
|
const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
|
|
169
|
+
correlationId,
|
|
634
170
|
writeLatestReceipt: false,
|
|
635
171
|
writeLatestProfile: false,
|
|
636
172
|
recordPerformanceHistory: false,
|
|
@@ -672,19 +208,19 @@ function entriesForScheduleBatch(entries, batch) {
|
|
|
672
208
|
const batchIntents = new Set(batch.intents);
|
|
673
209
|
return entries.filter((entry) => batchIntents.has(entry.intent));
|
|
674
210
|
}
|
|
675
|
-
async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets) {
|
|
211
|
+
async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets) {
|
|
676
212
|
const results = [];
|
|
677
213
|
for (const entry of entries) {
|
|
678
|
-
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
214
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
679
215
|
}
|
|
680
216
|
return results;
|
|
681
217
|
}
|
|
682
|
-
async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets) {
|
|
218
|
+
async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets) {
|
|
683
219
|
const results = [];
|
|
684
220
|
for (let index = 0; index < entries.length; index += parallelism) {
|
|
685
221
|
const chunk = entries.slice(index, index + parallelism);
|
|
686
222
|
const batchTracker = startRunWriteBatchTracking(projectRoot);
|
|
687
|
-
const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? [])));
|
|
223
|
+
const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? [])));
|
|
688
224
|
const writeDriftByIntent = finishRunWriteBatchTracking(batchTracker, chunk.map((entry) => ({
|
|
689
225
|
intentName: entry.intent,
|
|
690
226
|
declaredPaths: declaredWritePathsForScheduleEntry(entry),
|
|
@@ -732,7 +268,7 @@ function verificationResultFailed(result) {
|
|
|
732
268
|
result.status === 'start_failed' ||
|
|
733
269
|
result.status === 'output_limit_exceeded'));
|
|
734
270
|
}
|
|
735
|
-
async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism) {
|
|
271
|
+
async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, correlationId, scheduledTestTargets, parallelism) {
|
|
736
272
|
const results = [];
|
|
737
273
|
for (let batchIndex = 0; batchIndex < report.schedule.batches.length; batchIndex += 1) {
|
|
738
274
|
const batch = report.schedule.batches[batchIndex];
|
|
@@ -744,11 +280,11 @@ async function runScheduledVerificationIntents(report, projectRoot, lang, verifi
|
|
|
744
280
|
if (entries.length > 1 && entries.every((entry) => entry.parallelEligible)) {
|
|
745
281
|
batchResults =
|
|
746
282
|
parallelism > DEFAULT_VERIFY_PARALLELISM
|
|
747
|
-
? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets)
|
|
748
|
-
: await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
283
|
+
? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets)
|
|
284
|
+
: await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
|
|
749
285
|
}
|
|
750
286
|
else {
|
|
751
|
-
batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
287
|
+
batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
|
|
752
288
|
}
|
|
753
289
|
results.push(...batchResults);
|
|
754
290
|
if (!batchResults.some(verificationResultFailed)) {
|
|
@@ -1217,6 +753,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1217
753
|
const manifest = {
|
|
1218
754
|
schema_version: '1',
|
|
1219
755
|
command: 'verify',
|
|
756
|
+
correlation_id: outputWithReceiptPaths.correlation_id,
|
|
1220
757
|
reason: outputWithReceiptPaths.reason,
|
|
1221
758
|
reasons: outputWithReceiptPaths.reasons,
|
|
1222
759
|
plan_source: outputWithReceiptPaths.plan_source,
|
|
@@ -1237,6 +774,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1237
774
|
schema_version: '1',
|
|
1238
775
|
command: 'verify',
|
|
1239
776
|
kind: 'verify_run_summary',
|
|
777
|
+
correlation_id: outputWithReceiptPaths.correlation_id,
|
|
1240
778
|
reason: outputWithReceiptPaths.reason,
|
|
1241
779
|
reasons: outputWithReceiptPaths.reasons,
|
|
1242
780
|
plan_source: outputWithReceiptPaths.plan_source,
|
|
@@ -1269,7 +807,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1269
807
|
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, { verificationPlanId });
|
|
1270
808
|
const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
|
|
1271
809
|
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
1272
|
-
const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism);
|
|
810
|
+
const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, input.correlationId, scheduledTestTargets, parallelism);
|
|
1273
811
|
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
1274
812
|
const summary = summarizeResults(results);
|
|
1275
813
|
const status = getVerificationStatus(summary);
|
|
@@ -1325,6 +863,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1325
863
|
const output = {
|
|
1326
864
|
schema_version: VERIFY_SCHEMA_VERSION,
|
|
1327
865
|
command: 'verify',
|
|
866
|
+
correlation_id: input.correlationId,
|
|
1328
867
|
mustflow_root: projectRoot,
|
|
1329
868
|
reason: input.reasons.join(', '),
|
|
1330
869
|
reasons: input.reasons,
|
|
@@ -1359,16 +898,17 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
1359
898
|
return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
|
|
1360
899
|
});
|
|
1361
900
|
if (!firstEntry) {
|
|
1362
|
-
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
901
|
+
return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
|
|
1363
902
|
}
|
|
1364
903
|
const scheduledIntents = Array.from(new Set(report.schedule.entries.map((entry) => entry.intent)));
|
|
1365
904
|
const graphsByIntent = await readLocalCommandEffectGraphs(projectRoot, scheduledIntents);
|
|
1366
905
|
const firstGraph = graphsByIntent.get(firstEntry.intent);
|
|
1367
906
|
if (!firstGraph) {
|
|
1368
|
-
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
907
|
+
return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
|
|
1369
908
|
}
|
|
1370
909
|
return {
|
|
1371
910
|
...report,
|
|
911
|
+
correlation_id: input.correlationId,
|
|
1372
912
|
verification_plan_id: verificationPlanId,
|
|
1373
913
|
requirements,
|
|
1374
914
|
schedule: {
|
|
@@ -1475,7 +1015,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1475
1015
|
let externalChecks = [];
|
|
1476
1016
|
try {
|
|
1477
1017
|
if (parsed.writePlan) {
|
|
1478
|
-
|
|
1018
|
+
resolveVerifyInputPath(projectRoot, parsed.writePlan);
|
|
1479
1019
|
}
|
|
1480
1020
|
if (parsed.changed) {
|
|
1481
1021
|
const changedInput = createInputFromChanged(projectRoot);
|
|
@@ -1487,6 +1027,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1487
1027
|
}
|
|
1488
1028
|
else {
|
|
1489
1029
|
input = {
|
|
1030
|
+
correlationId: createCorrelationId('verify'),
|
|
1490
1031
|
reasons: [parsed.reason],
|
|
1491
1032
|
classificationReport: createSyntheticClassificationReport([parsed.reason]),
|
|
1492
1033
|
};
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -652,6 +652,7 @@ Read these files before working:
|
|
|
652
652
|
"run.help.option.dryRun": "Print a non-executing command plan",
|
|
653
653
|
"run.help.option.planOnly": "Alias for --dry-run",
|
|
654
654
|
"run.help.option.json": "Print the run record or command plan as JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Allow one execution from a root with a missing or invalid manifest lock after manual review",
|
|
655
656
|
"run.help.exit.ok": "The command completed with an allowed exit code",
|
|
656
657
|
"run.help.exit.fail": "The command was invalid, refused, timed out, or failed",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Suggested command contract snippet",
|
|
@@ -674,6 +675,8 @@ Read these files before working:
|
|
|
674
675
|
"run.error.maxOutputBytes": 'Command "{intent}" has invalid max_output_bytes. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "The output limit must stay within the allowed maximum.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Use either --dry-run or --plan-only, not both",
|
|
678
|
+
"run.error.untrustedRootMissing": "Refused to execute commands because {path} is missing. Run mf init/update to install the workflow, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Refused to execute commands because the manifest lock is invalid: {detail}. Restore or regenerate it, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'Command "{intent}" timed out after {seconds} seconds',
|
|
678
681
|
"run.error.outputLimitExceeded": 'Command "{intent}" exceeded max_output_bytes: {message}',
|
|
679
682
|
"run.error.startFailed": 'Command "{intent}" failed to start: {message}',
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -652,6 +652,7 @@ Lee estos archivos antes de trabajar:
|
|
|
652
652
|
"run.help.option.dryRun": "Imprime un plan de comando sin ejecutarlo",
|
|
653
653
|
"run.help.option.planOnly": "Alias de --dry-run",
|
|
654
654
|
"run.help.option.json": "Imprime el registro de ejecución o el plan de comando como JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Permite una ejecución desde una raíz con bloqueo de manifiesto ausente o inválido tras revisión manual",
|
|
655
656
|
"run.help.exit.ok": "El comando se completo con un codigo de salida permitido",
|
|
656
657
|
"run.help.exit.fail": "El comando no era válido, fue rechazado, agotó el tiempo o falló",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Snippet sugerido para el contrato de comandos",
|
|
@@ -674,6 +675,8 @@ Lee estos archivos antes de trabajar:
|
|
|
674
675
|
"run.error.maxOutputBytes": 'El comando "{intent}" tiene max_output_bytes no válido. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "El límite de salida debe permanecer dentro del máximo permitido.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Usa --dry-run o --plan-only, no ambos",
|
|
678
|
+
"run.error.untrustedRootMissing": "Se rechazó ejecutar comandos porque falta {path}. Ejecuta mf init/update para instalar el flujo, o usa --allow-untrusted-root tras revisar AGENTS.md y .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Se rechazó ejecutar comandos porque el bloqueo de manifiesto no es válido: {detail}. Restáuralo o regenéralo, o usa --allow-untrusted-root tras revisar AGENTS.md y .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'El comando "{intent}" agotó el tiempo después de {seconds} segundos',
|
|
678
681
|
"run.error.outputLimitExceeded": 'El comando "{intent}" superó max_output_bytes: {message}',
|
|
679
682
|
"run.error.startFailed": 'No se pudo iniciar el comando "{intent}": {message}',
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -652,6 +652,7 @@ Lisez ces fichiers avant de travailler :
|
|
|
652
652
|
"run.help.option.dryRun": "Imprime un plan de commande sans l'exécuter",
|
|
653
653
|
"run.help.option.planOnly": "Alias de --dry-run",
|
|
654
654
|
"run.help.option.json": "Imprime l'enregistrement d'exécution ou le plan de commande en JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Autorise une seule exécution depuis une racine sans verrou de manifeste valide après revue manuelle",
|
|
655
656
|
"run.help.exit.ok": "La commande s'est terminée avec un code de sortie autorisé",
|
|
656
657
|
"run.help.exit.fail": "La commande était non valide, refusée, expirée ou a échoué",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Extrait suggéré de contrat de commande",
|
|
@@ -674,6 +675,8 @@ Lisez ces fichiers avant de travailler :
|
|
|
674
675
|
"run.error.maxOutputBytes": 'La commande "{intent}" a une valeur max_output_bytes non valide. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "La limite de sortie doit rester dans le maximum autorisé.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Utilisez --dry-run ou --plan-only, pas les deux",
|
|
678
|
+
"run.error.untrustedRootMissing": "Exécution refusée car {path} est absent. Lancez mf init/update pour installer le workflow, ou ajoutez --allow-untrusted-root après avoir relu AGENTS.md et .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Exécution refusée car le verrou de manifeste est invalide : {detail}. Restaurez-le ou régénérez-le, ou ajoutez --allow-untrusted-root après avoir relu AGENTS.md et .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'La commande "{intent}" a expiré après {seconds} secondes',
|
|
678
681
|
"run.error.outputLimitExceeded": 'La commande "{intent}" a dépassé max_output_bytes : {message}',
|
|
679
682
|
"run.error.startFailed": 'Impossible de démarrer la commande "{intent}" : {message}',
|