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.
Files changed (42) hide show
  1. package/README.md +8 -0
  2. package/dist/cli/commands/classify.js +2 -0
  3. package/dist/cli/commands/dashboard.js +9 -69
  4. package/dist/cli/commands/run/receipt.js +1 -0
  5. package/dist/cli/commands/run.js +14 -1
  6. package/dist/cli/commands/verify/evidence-input.js +269 -0
  7. package/dist/cli/commands/verify/input.js +212 -0
  8. package/dist/cli/commands/verify.js +23 -482
  9. package/dist/cli/i18n/en.js +3 -0
  10. package/dist/cli/i18n/es.js +3 -0
  11. package/dist/cli/i18n/fr.js +3 -0
  12. package/dist/cli/i18n/hi.js +3 -0
  13. package/dist/cli/i18n/ko.js +3 -0
  14. package/dist/cli/i18n/zh.js +3 -0
  15. package/dist/cli/lib/dashboard-export.js +2 -0
  16. package/dist/cli/lib/dashboard-mutations.js +79 -0
  17. package/dist/cli/lib/local-index/command-effect-index.js +25 -0
  18. package/dist/cli/lib/local-index/hashing.js +7 -0
  19. package/dist/cli/lib/local-index/index.js +127 -826
  20. package/dist/cli/lib/local-index/source-index.js +137 -0
  21. package/dist/cli/lib/local-index/verification-evidence.js +451 -0
  22. package/dist/cli/lib/local-index/workflow-documents.js +204 -0
  23. package/dist/cli/lib/run-root-trust.js +27 -0
  24. package/dist/core/change-classification-policy.js +47 -0
  25. package/dist/core/change-classification.js +10 -43
  26. package/dist/core/contract-lint.js +6 -2
  27. package/dist/core/correlation-id.js +16 -0
  28. package/dist/core/run-receipt.js +1 -0
  29. package/package.json +4 -1
  30. package/schemas/README.md +4 -0
  31. package/schemas/change-verification-report.schema.json +4 -0
  32. package/schemas/classify-report.schema.json +4 -0
  33. package/schemas/dashboard-export.schema.json +4 -0
  34. package/schemas/latest-run-pointer.schema.json +4 -0
  35. package/schemas/run-receipt.schema.json +4 -0
  36. package/schemas/verify-report.schema.json +4 -0
  37. package/schemas/verify-run-manifest.schema.json +4 -0
  38. package/templates/default/i18n.toml +3 -3
  39. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
  40. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
  41. package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
  42. package/templates/default/manifest.toml +1 -1
package/README.md CHANGED
@@ -367,6 +367,12 @@ bun run docs:check
367
367
  bun run check:install
368
368
  ```
369
369
 
370
+ When Bun is not available, maintainers can still run the core CLI and package metadata checks with Node/npm:
371
+
372
+ ```sh
373
+ npm run check:core:node
374
+ ```
375
+
370
376
  Agents working in this repository should prefer the configured mustflow intents for routine verification.
371
377
 
372
378
  ```sh
@@ -376,6 +382,7 @@ mf run test_related
376
382
  mf run test
377
383
  mf run test_coverage
378
384
  mf run test_release
385
+ mf run maintainer_check_node
379
386
  mf run docs_validate_fast
380
387
  mf run docs_validate
381
388
  mf run mustflow_check
@@ -413,6 +420,7 @@ The npm package includes only:
413
420
  dist/
414
421
  templates/
415
422
  schemas/
423
+ examples/
416
424
  README.md
417
425
  LICENSE
418
426
  ```
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { createChangeClassificationReport, } from '../../core/change-classification.js';
3
+ import { createCorrelationId } from '../../core/correlation-id.js';
3
4
  import { writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
4
5
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
5
6
  import { requireGitChangedFiles } from '../lib/git-changes.js';
@@ -71,6 +72,7 @@ export function createClassifyOutput(projectRoot, source, paths) {
71
72
  return {
72
73
  schema_version: CLASSIFY_SCHEMA_VERSION,
73
74
  command: 'classify',
75
+ correlation_id: createCorrelationId('classify'),
74
76
  mustflow_root: projectRoot,
75
77
  ...createChangeClassificationReport(source, files),
76
78
  };
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
2
2
  import { existsSync, readFileSync, statSync } from 'node:fs';
3
3
  import http from 'node:http';
4
4
  import path from 'node:path';
5
- import { openPathInFileManager, openUrlInBrowser } from '../lib/browser-open.js';
5
+ import { openUrlInBrowser } from '../lib/browser-open.js';
6
6
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
7
7
  import { renderDashboardHtml, } from '../lib/dashboard-html.js';
8
8
  import { DashboardExportPathError, writeDashboardExport, } from '../lib/dashboard-export.js';
@@ -12,8 +12,9 @@ import { parseSkillIndexRoutes } from '../../core/skill-route-alignment.js';
12
12
  import { getAgentContext } from '../lib/agent-context.js';
13
13
  import { readGitChangedFiles } from '../lib/git-changes.js';
14
14
  import { isRecord, readCommandContract, readPositiveInteger, readString, readStringArray, } from '../lib/command-contract.js';
15
- import { readDashboardPreferences, updateDashboardPreferences, } from '../lib/dashboard-preferences.js';
16
- import { DOC_REVIEW_LEDGER_RELATIVE_PATH, isDocReviewStatus, isReviewerKind, listDocReviewEntries, markDocReviewEntry, } from '../lib/doc-review-ledger.js';
15
+ import { readDashboardPreferences, } from '../lib/dashboard-preferences.js';
16
+ import { DOC_REVIEW_LEDGER_RELATIVE_PATH, isDocReviewStatus, listDocReviewEntries, } from '../lib/doc-review-ledger.js';
17
+ import { markDashboardDocReviewFromPayload, openDashboardMustflowFolder, updateDashboardPreferencesFromPayload, } from '../lib/dashboard-mutations.js';
17
18
  import { MANIFEST_LOCK_RELATIVE_PATH, inspectManifestLock } from '../lib/manifest-lock.js';
18
19
  import { getLocalIndexDatabasePath, readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
19
20
  import { readPackageMetadata } from '../lib/package-info.js';
@@ -26,7 +27,6 @@ const DEFAULT_DASHBOARD_HOST = '127.0.0.1';
26
27
  const DEFAULT_DASHBOARD_PORT = 0;
27
28
  const MAX_REQUEST_BYTES = 64 * 1024;
28
29
  const LOCAL_DASHBOARD_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
29
- const DOC_REVIEW_BULK_PAYLOAD_FIELDS = ['paths', 'documents', 'entries'];
30
30
  const RELEASE_FILE_PATTERNS = [
31
31
  /^package\.json$/u,
32
32
  /^bun\.lockb?$/u,
@@ -323,66 +323,6 @@ async function readRequestJson(request) {
323
323
  const rawBody = Buffer.concat(chunks).toString('utf8');
324
324
  return rawBody.trim().length === 0 ? {} : JSON.parse(rawBody);
325
325
  }
326
- function readUpdatePayload(value) {
327
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
328
- throw new Error('Request body must be a JSON object.');
329
- }
330
- const updates = value.updates;
331
- if (!Array.isArray(updates)) {
332
- throw new Error('Request body must include an updates array.');
333
- }
334
- return updates.map((entry) => {
335
- if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
336
- throw new Error('Each update must be a JSON object.');
337
- }
338
- const update = entry;
339
- if (typeof update.id !== 'string' || update.id.trim().length === 0) {
340
- throw new Error('Each update must include an id.');
341
- }
342
- return { id: update.id, value: update.value };
343
- });
344
- }
345
- function readOptionalStringField(value, key) {
346
- const field = value[key];
347
- return typeof field === 'string' && field.trim().length > 0 ? field.trim() : undefined;
348
- }
349
- function readRequiredStringField(value, key) {
350
- const field = readOptionalStringField(value, key);
351
- if (!field) {
352
- throw new Error(`${key} is required.`);
353
- }
354
- return field;
355
- }
356
- function readDocReviewPayload(value) {
357
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
358
- throw new Error('Request body must be a JSON object.');
359
- }
360
- const payload = value;
361
- for (const field of DOC_REVIEW_BULK_PAYLOAD_FIELDS) {
362
- if (field in payload) {
363
- throw new Error('Bulk documentation review updates require a separate confirmed flow.');
364
- }
365
- }
366
- const status = readRequiredStringField(payload, 'status');
367
- if (status !== 'approved' && status !== 'needs_human' && status !== 'ignored') {
368
- throw new Error('status must be approved, needs_human, or ignored.');
369
- }
370
- const reviewerKind = readRequiredStringField(payload, 'reviewerKind');
371
- if (!isReviewerKind(reviewerKind)) {
372
- throw new Error('reviewerKind must be human, llm, tool, or external.');
373
- }
374
- return {
375
- path: readRequiredStringField(payload, 'path'),
376
- status,
377
- reviewerKind,
378
- reviewerId: readRequiredStringField(payload, 'reviewerId'),
379
- reviewerLabel: readOptionalStringField(payload, 'reviewerLabel'),
380
- reviewerProvider: readOptionalStringField(payload, 'reviewerProvider'),
381
- reviewerModel: readOptionalStringField(payload, 'reviewerModel'),
382
- reviewerCommandIntent: readOptionalStringField(payload, 'reviewerCommandIntent'),
383
- summary: readOptionalStringField(payload, 'summary'),
384
- };
385
- }
386
326
  function renderDocReviewResponse(projectRoot, requestUrl) {
387
327
  const status = requestUrl.searchParams.get('status');
388
328
  if (status && !isDocReviewStatus(status)) {
@@ -858,7 +798,7 @@ export async function runDashboard(args, reporter, lang = 'en') {
858
798
  }
859
799
  if (request.method === 'POST') {
860
800
  const body = await readRequestJson(request);
861
- sendJson(response, 200, updateDashboardPreferences(projectRoot, readUpdatePayload(body)));
801
+ sendJson(response, 200, updateDashboardPreferencesFromPayload(projectRoot, body));
862
802
  return;
863
803
  }
864
804
  }
@@ -871,12 +811,12 @@ export async function runDashboard(args, reporter, lang = 'en') {
871
811
  sendText(response, 405, 'Method not allowed');
872
812
  return;
873
813
  }
874
- const mustflowPath = path.join(projectRoot, '.mustflow');
875
- if (!existsSync(mustflowPath)) {
814
+ const openResult = openDashboardMustflowFolder(projectRoot);
815
+ if (openResult === 'missing') {
876
816
  sendText(response, 404, '.mustflow folder not found');
877
817
  return;
878
818
  }
879
- if (!openPathInFileManager(mustflowPath)) {
819
+ if (openResult === 'unavailable') {
880
820
  sendText(response, 500, 'No file manager opener is available for this platform');
881
821
  return;
882
822
  }
@@ -894,7 +834,7 @@ export async function runDashboard(args, reporter, lang = 'en') {
894
834
  }
895
835
  if (request.method === 'POST') {
896
836
  const body = await readRequestJson(request);
897
- markDocReviewEntry(projectRoot, readDocReviewPayload(body));
837
+ markDashboardDocReviewFromPayload(projectRoot, body);
898
838
  sendJson(response, 200, renderDocReviewResponse(projectRoot, requestUrl));
899
839
  return;
900
840
  }
@@ -1,6 +1,7 @@
1
1
  import { createRunReceipt, createRunReceiptRelativePath, } from '../../../core/run-receipt.js';
2
2
  export function assembleRunReceipt(input) {
3
3
  return createRunReceipt({
4
+ correlationId: input.correlationId,
4
5
  intent: input.intentName,
5
6
  status: input.runStatus,
6
7
  timedOut: input.runStatus === 'timed_out',
@@ -1,10 +1,12 @@
1
1
  import { performance } from 'node:perf_hooks';
2
2
  import { createCommandEnv } from '../../core/command-env.js';
3
+ import { createCorrelationId } from '../../core/correlation-id.js';
3
4
  import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
4
5
  import { readCommandContract, readMustflowConfigIfExists } from '../../core/config-loading.js';
5
6
  import { resolveRunReceiptRetentionPolicy } from '../../core/retention-policy.js';
6
7
  import { t } from '../lib/i18n.js';
7
8
  import { resolveMustflowRoot } from '../lib/project-root.js';
9
+ import { ALLOW_UNTRUSTED_ROOT_OPTION, assessRunRootTrust } from '../lib/run-root-trust.js';
8
10
  import { createRunPlan, createRunPreview, renderRunPreviewText, } from '../lib/run-plan.js';
9
11
  import { writeRunReceipt, } from '../../core/run-receipt.js';
10
12
  import { recordRunPerformanceHistory } from '../../core/run-performance-history.js';
@@ -92,6 +94,7 @@ export function getRunHelp(lang = 'en') {
92
94
  { label: '--dry-run', description: t(lang, 'run.help.option.dryRun') },
93
95
  { label: '--plan-only', description: t(lang, 'run.help.option.planOnly') },
94
96
  { label: '--json', description: t(lang, 'run.help.option.json') },
97
+ { label: ALLOW_UNTRUSTED_ROOT_OPTION, description: t(lang, 'run.help.option.allowUntrustedRoot') },
95
98
  { label: '-h, --help', description: t(lang, 'cli.option.help') },
96
99
  ],
97
100
  examples: ['mf run test', 'mf run lint --json', 'mf run mustflow_check --dry-run --json'],
@@ -121,7 +124,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
121
124
  reporter.stdout(getRunHelp(lang));
122
125
  return 0;
123
126
  }
124
- const supportedOptions = new Set(['--json', '--dry-run', '--plan-only']);
127
+ const supportedOptions = new Set(['--json', '--dry-run', '--plan-only', ALLOW_UNTRUSTED_ROOT_OPTION]);
125
128
  const unsupported = args.filter((arg) => arg.startsWith('-') && !supportedOptions.has(arg));
126
129
  if (unsupported.length > 0) {
127
130
  printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf run --help', getRunHelp(lang), lang);
@@ -130,6 +133,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
130
133
  const json = args.includes('--json');
131
134
  const dryRun = args.includes('--dry-run');
132
135
  const planOnly = args.includes('--plan-only');
136
+ const allowUntrustedRoot = args.includes(ALLOW_UNTRUSTED_ROOT_OPTION);
133
137
  const previewMode = dryRun ? 'dry-run' : planOnly ? 'plan-only' : null;
134
138
  if (dryRun && planOnly) {
135
139
  printUsageError(reporter, t(lang, 'run.error.conflictingPreviewModes'), 'mf run --help', getRunHelp(lang), lang);
@@ -146,6 +150,14 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
146
150
  return 1;
147
151
  }
148
152
  const projectRoot = profiler.measure('root_detection', () => resolveMustflowRoot());
153
+ const rootTrust = profiler.measure('root_trust', () => assessRunRootTrust(projectRoot));
154
+ if (!previewMode && !allowUntrustedRoot && !rootTrust.trusted) {
155
+ const message = rootTrust.reason === 'manifest_lock_invalid'
156
+ ? t(lang, 'run.error.untrustedRootInvalid', { detail: rootTrust.detail ?? rootTrust.manifestLockPath })
157
+ : t(lang, 'run.error.untrustedRootMissing', { path: rootTrust.detail ?? rootTrust.manifestLockPath });
158
+ reporter.stderr(renderCliError(message, 'mf run --help', lang));
159
+ return 1;
160
+ }
149
161
  const contract = profiler.measure('command_contract', () => readCommandContract(projectRoot));
150
162
  const plan = profiler.measure('plan_creation', () => createRunPlan(projectRoot, contract, intentName, { testTargets: options.testTargets }));
151
163
  if (previewMode) {
@@ -208,6 +220,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
208
220
  }
209
221
  }
210
222
  const receipt = profiler.measure('receipt_create', () => assembleRunReceipt({
223
+ correlationId: options.correlationId ?? createCorrelationId('run'),
211
224
  intentName,
212
225
  runStatus,
213
226
  startedAt,
@@ -0,0 +1,269 @@
1
+ import { readVerifyJsonInputFile } from './input.js';
2
+ function isPlainRecord(value) {
3
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
4
+ }
5
+ function isExternalEvidenceStatus(value) {
6
+ return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
7
+ }
8
+ function isLegacyReproEvidenceStatus(value) {
9
+ return value === 'present' || value === 'unavailable' || value === 'missing';
10
+ }
11
+ function isReproBeforeFixStatus(value) {
12
+ return value === 'reproduced' || value === 'unavailable' || value === 'missing';
13
+ }
14
+ function isReproBeforeFixOutcome(value) {
15
+ return value === 'failed_as_expected' || value === 'failed_differently' || value === 'passed_unexpectedly' || value === null;
16
+ }
17
+ function isReproAfterFixStatus(value) {
18
+ return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
19
+ }
20
+ function isReproAfterFixOutcome(value) {
21
+ return value === 'passed_expected_behavior' || value === 'failed_same_route' || value === 'failed_differently' || value === null;
22
+ }
23
+ function isReproRegressionGuardStatus(value) {
24
+ return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
25
+ }
26
+ function isReproRouteKind(value) {
27
+ return (value === 'test' ||
28
+ value === 'cli' ||
29
+ value === 'browser' ||
30
+ value === 'api' ||
31
+ value === 'manual' ||
32
+ value === 'unknown' ||
33
+ value === null);
34
+ }
35
+ function readOptionalString(value) {
36
+ return typeof value === 'string' && value.length > 0 ? value : null;
37
+ }
38
+ function readRouteStep(value, index) {
39
+ if (!isPlainRecord(value)) {
40
+ return {
41
+ ordinal: index + 1,
42
+ action: null,
43
+ target: null,
44
+ input_digest: null,
45
+ observation_digest: null,
46
+ summary: null,
47
+ };
48
+ }
49
+ const ordinal = typeof value.ordinal === 'number' && Number.isInteger(value.ordinal) && value.ordinal > 0 ? value.ordinal : index + 1;
50
+ return {
51
+ ordinal,
52
+ action: readOptionalString(value.action),
53
+ target: readOptionalString(value.target),
54
+ input_digest: readOptionalString(value.input_digest),
55
+ observation_digest: readOptionalString(value.observation_digest),
56
+ summary: readOptionalString(value.summary),
57
+ };
58
+ }
59
+ function readReproductionRoute(value) {
60
+ if (!isPlainRecord(value)) {
61
+ return {
62
+ route_id: null,
63
+ route_kind: null,
64
+ route_digest: null,
65
+ failure_oracle_hash: null,
66
+ steps: [],
67
+ };
68
+ }
69
+ const routeKind = value.route_kind ?? null;
70
+ if (!isReproRouteKind(routeKind)) {
71
+ throw new Error('invalid_repro_evidence_file');
72
+ }
73
+ const rawSteps = Array.isArray(value.steps) ? value.steps : [];
74
+ return {
75
+ route_id: readOptionalString(value.route_id),
76
+ route_kind: routeKind,
77
+ route_digest: readOptionalString(value.route_digest),
78
+ failure_oracle_hash: readOptionalString(value.failure_oracle_hash),
79
+ steps: rawSteps.map((step, index) => readRouteStep(step, index)),
80
+ };
81
+ }
82
+ function readLegacyReproEvidenceItem(value) {
83
+ if (!isPlainRecord(value)) {
84
+ return {
85
+ status: 'missing',
86
+ summary: null,
87
+ reason: null,
88
+ };
89
+ }
90
+ if (!isLegacyReproEvidenceStatus(value.status)) {
91
+ throw new Error('invalid_repro_evidence_file');
92
+ }
93
+ return {
94
+ status: value.status,
95
+ summary: readOptionalString(value.summary),
96
+ reason: readOptionalString(value.reason),
97
+ };
98
+ }
99
+ function legacyBeforeFixEvidence(value) {
100
+ const item = readLegacyReproEvidenceItem(value);
101
+ return {
102
+ status: item.status === 'present' ? 'reproduced' : item.status,
103
+ outcome: item.status === 'present' ? 'failed_as_expected' : null,
104
+ receipt_path: null,
105
+ receipt_sha256: null,
106
+ verification_plan_id: null,
107
+ summary: item.summary,
108
+ reason: item.reason,
109
+ };
110
+ }
111
+ function legacyAfterFixEvidence(value) {
112
+ const item = readLegacyReproEvidenceItem(value);
113
+ return {
114
+ status: item.status === 'present' ? 'passed' : item.status,
115
+ outcome: item.status === 'present' ? 'passed_expected_behavior' : null,
116
+ same_route_as: null,
117
+ receipt_path: null,
118
+ receipt_sha256: null,
119
+ verification_plan_id: null,
120
+ summary: item.summary,
121
+ reason: item.reason,
122
+ };
123
+ }
124
+ function legacyRegressionGuardEvidence(value) {
125
+ const item = readLegacyReproEvidenceItem(value);
126
+ return {
127
+ status: item.status === 'present' ? 'passed' : item.status,
128
+ intent: null,
129
+ test_path: null,
130
+ receipt_path: null,
131
+ receipt_sha256: null,
132
+ verification_plan_id: null,
133
+ summary: item.summary,
134
+ reason: item.reason,
135
+ };
136
+ }
137
+ function readBeforeFixEvidence(value) {
138
+ if (!isPlainRecord(value)) {
139
+ return {
140
+ status: 'missing',
141
+ outcome: null,
142
+ receipt_path: null,
143
+ receipt_sha256: null,
144
+ verification_plan_id: null,
145
+ summary: null,
146
+ reason: null,
147
+ };
148
+ }
149
+ const outcome = value.outcome ?? null;
150
+ if (!isReproBeforeFixStatus(value.status) || !isReproBeforeFixOutcome(outcome)) {
151
+ throw new Error('invalid_repro_evidence_file');
152
+ }
153
+ return {
154
+ status: value.status,
155
+ outcome,
156
+ receipt_path: readOptionalString(value.receipt_path),
157
+ receipt_sha256: readOptionalString(value.receipt_sha256),
158
+ verification_plan_id: readOptionalString(value.verification_plan_id),
159
+ summary: readOptionalString(value.summary),
160
+ reason: readOptionalString(value.reason),
161
+ };
162
+ }
163
+ function readAfterFixEvidence(value) {
164
+ if (!isPlainRecord(value)) {
165
+ return {
166
+ status: 'missing',
167
+ outcome: null,
168
+ same_route_as: null,
169
+ receipt_path: null,
170
+ receipt_sha256: null,
171
+ verification_plan_id: null,
172
+ summary: null,
173
+ reason: null,
174
+ };
175
+ }
176
+ const outcome = value.outcome ?? null;
177
+ if (!isReproAfterFixStatus(value.status) || !isReproAfterFixOutcome(outcome)) {
178
+ throw new Error('invalid_repro_evidence_file');
179
+ }
180
+ return {
181
+ status: value.status,
182
+ outcome,
183
+ same_route_as: readOptionalString(value.same_route_as),
184
+ receipt_path: readOptionalString(value.receipt_path),
185
+ receipt_sha256: readOptionalString(value.receipt_sha256),
186
+ verification_plan_id: readOptionalString(value.verification_plan_id),
187
+ summary: readOptionalString(value.summary),
188
+ reason: readOptionalString(value.reason),
189
+ };
190
+ }
191
+ function readRegressionGuardEvidence(value) {
192
+ if (!isPlainRecord(value)) {
193
+ return {
194
+ status: 'missing',
195
+ intent: null,
196
+ test_path: null,
197
+ receipt_path: null,
198
+ receipt_sha256: null,
199
+ verification_plan_id: null,
200
+ summary: null,
201
+ reason: null,
202
+ };
203
+ }
204
+ if (!isReproRegressionGuardStatus(value.status)) {
205
+ throw new Error('invalid_repro_evidence_file');
206
+ }
207
+ return {
208
+ status: value.status,
209
+ intent: readOptionalString(value.intent),
210
+ test_path: readOptionalString(value.test_path),
211
+ receipt_path: readOptionalString(value.receipt_path),
212
+ receipt_sha256: readOptionalString(value.receipt_sha256),
213
+ verification_plan_id: readOptionalString(value.verification_plan_id),
214
+ summary: readOptionalString(value.summary),
215
+ reason: readOptionalString(value.reason),
216
+ };
217
+ }
218
+ export function readReproEvidenceFile(projectRoot, inputPath) {
219
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_repro_evidence_file');
220
+ if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
221
+ throw new Error('unsupported_repro_evidence_source');
222
+ }
223
+ const regressionGuard = isPlainRecord(parsed.regression_guard) && isReproRegressionGuardStatus(parsed.regression_guard.status)
224
+ ? readRegressionGuardEvidence(parsed.regression_guard)
225
+ : legacyRegressionGuardEvidence(parsed.regression_guard);
226
+ return {
227
+ source: 'repro_first_debug',
228
+ authority: 'claim_evidence',
229
+ reported_symptom: readOptionalString(parsed.reported_symptom),
230
+ expected_behavior: readOptionalString(parsed.expected_behavior),
231
+ observed_behavior: readOptionalString(parsed.observed_behavior),
232
+ reproduction_route: readReproductionRoute(parsed.reproduction_route),
233
+ before_fix: isPlainRecord(parsed.before_fix)
234
+ ? readBeforeFixEvidence(parsed.before_fix)
235
+ : legacyBeforeFixEvidence(parsed.evidence_before_fix),
236
+ after_fix: isPlainRecord(parsed.after_fix)
237
+ ? readAfterFixEvidence(parsed.after_fix)
238
+ : legacyAfterFixEvidence(parsed.evidence_after_fix),
239
+ regression_guard: regressionGuard,
240
+ };
241
+ }
242
+ export function readExternalEvidenceFile(projectRoot, inputPath) {
243
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_external_evidence_file');
244
+ if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
245
+ throw new Error('unsupported_external_evidence_source');
246
+ }
247
+ if (!Array.isArray(parsed.checks)) {
248
+ throw new Error('invalid_external_evidence_file');
249
+ }
250
+ return parsed.checks.map((check) => {
251
+ if (!isPlainRecord(check) ||
252
+ typeof check.provider !== 'string' ||
253
+ check.provider.length === 0 ||
254
+ typeof check.name !== 'string' ||
255
+ check.name.length === 0 ||
256
+ !isExternalEvidenceStatus(check.status)) {
257
+ throw new Error('invalid_external_evidence_file');
258
+ }
259
+ return {
260
+ source: 'external_ci',
261
+ authority: 'supporting_only',
262
+ provider: check.provider,
263
+ name: check.name,
264
+ status: check.status,
265
+ url: readOptionalString(check.url),
266
+ summary: readOptionalString(check.summary),
267
+ };
268
+ });
269
+ }