mustflow 2.11.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -353,6 +353,25 @@ function queryRows(database, sql, params = []) {
353
353
  return row;
354
354
  });
355
355
  }
356
+ const VALIDATION_RATCHET_RISK_CODES = new Set([
357
+ 'related_test_deleted',
358
+ 'skip_or_only_marker_present',
359
+ 'todo_or_pending_marker_added',
360
+ 'assertion_count_decreased',
361
+ 'assertion_matcher_weakened',
362
+ 'negative_assertion_removed',
363
+ 'exception_assertion_removed',
364
+ 'snapshot_mass_updated',
365
+ 'golden_output_replaced',
366
+ 'verification_intent_disabled',
367
+ 'verification_required_after_removed',
368
+ 'success_exit_codes_widened',
369
+ 'command_allows_no_tests',
370
+ 'command_forces_snapshot_update',
371
+ 'command_hides_failure',
372
+ 'coverage_threshold_lowered',
373
+ 'test_selection_narrowed',
374
+ ]);
356
375
  function searchCapabilities(fts5Available) {
357
376
  return {
358
377
  backend: fts5Available ? SEARCH_BACKEND_FTS5 : SEARCH_BACKEND_TABLE_SCAN,
@@ -410,6 +429,34 @@ function stringArrayField(record, key) {
410
429
  function joinedList(values) {
411
430
  return [...values].sort((left, right) => left.localeCompare(right)).join(', ');
412
431
  }
432
+ function hashJson(value) {
433
+ return sha256Text(JSON.stringify(value));
434
+ }
435
+ function stringListHash(values) {
436
+ const normalized = values.filter((value) => typeof value === 'string' && value.length > 0);
437
+ return normalized.length > 0 ? hashJson([...normalized].sort((left, right) => left.localeCompare(right))) : null;
438
+ }
439
+ function reproObservation(routeId, phase, evidence) {
440
+ const status = stringField(evidence, 'status');
441
+ const outcome = stringField(evidence, 'outcome') ?? status;
442
+ const receiptHash = stringField(evidence, 'receipt_sha256');
443
+ const diagnosticFingerprint = stringField(evidence, 'diagnostic_fingerprint') ??
444
+ stringField(evidence, 'diagnostic_hash') ??
445
+ hashJson({
446
+ phase,
447
+ status,
448
+ outcome,
449
+ summary: stringField(evidence, 'summary'),
450
+ reason: stringField(evidence, 'reason'),
451
+ });
452
+ return {
453
+ routeId,
454
+ phase,
455
+ outcome,
456
+ receiptHash,
457
+ diagnosticFingerprint,
458
+ };
459
+ }
413
460
  function evidenceStatusForRunReceipt(latest) {
414
461
  return stringField(latest, 'status') ?? (booleanField(latest, 'timed_out') ? 'timed_out' : 'unknown');
415
462
  }
@@ -444,20 +491,38 @@ function createVerificationEvidenceIndex(projectRoot) {
444
491
  if (!existsSync(latestPath)) {
445
492
  return {
446
493
  summaries: [],
494
+ verificationPlans: [],
495
+ acceptanceCriteria: [],
496
+ criterionCoverage: [],
447
497
  receipts: [],
498
+ commandReceiptSummaries: [],
448
499
  coverageStates: [],
449
500
  riskSignals: [],
501
+ validationRatchetSignals: [],
502
+ completionVerdictSummaries: [],
450
503
  failureFingerprints: [],
504
+ reproRoutes: [],
505
+ reproObservations: [],
506
+ failureFingerprintReadModels: [],
451
507
  };
452
508
  }
453
509
  const latest = readJsonRecord(latestPath);
454
510
  if (!latest) {
455
511
  return {
456
512
  summaries: [],
513
+ verificationPlans: [],
514
+ acceptanceCriteria: [],
515
+ criterionCoverage: [],
457
516
  receipts: [],
517
+ commandReceiptSummaries: [],
458
518
  coverageStates: [],
459
519
  riskSignals: [],
520
+ validationRatchetSignals: [],
521
+ completionVerdictSummaries: [],
460
522
  failureFingerprints: [],
523
+ reproRoutes: [],
524
+ reproObservations: [],
525
+ failureFingerprintReadModels: [],
461
526
  };
462
527
  }
463
528
  const sourceHash = sha256Bytes(readFileSync(latestPath));
@@ -472,7 +537,13 @@ function createVerificationEvidenceIndex(projectRoot) {
472
537
  const completionStatus = stringField(completionVerdict, 'status');
473
538
  const rawReceipts = recordArrayField(evidenceModel, 'receipts');
474
539
  const rawCoverage = recordArrayField(evidenceModel, 'coverage_matrix');
540
+ const rawRequirements = recordArrayField(evidenceModel, 'requirements');
475
541
  const rawRisks = recordArrayField(evidenceModel, 'remaining_risks');
542
+ const recordedFailureFingerprintRecord = recordField(latest, 'failure_fingerprint');
543
+ const repeatedFailureSummary = recordField(latest, 'repeated_failure_summary');
544
+ const reproEvidence = recordField(latest, 'repro_evidence') ?? recordField(evidenceModel, 'repro_evidence');
545
+ const reproductionRoute = recordField(reproEvidence, 'reproduction_route');
546
+ const recordedFailureFingerprint = stringField(recordedFailureFingerprintRecord, 'fingerprint');
476
547
  const receipts = rawReceipts.length > 0
477
548
  ? rawReceipts.map((receipt, index) => ({
478
549
  sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
@@ -483,6 +554,12 @@ function createVerificationEvidenceIndex(projectRoot) {
483
554
  verificationPlanId: stringField(receipt, 'verification_plan_id'),
484
555
  receiptPath: stringField(receipt, 'receipt_path'),
485
556
  receiptSha256: stringField(receipt, 'receipt_sha256'),
557
+ commandFingerprint: stringField(receipt, 'command_fingerprint'),
558
+ contractFingerprint: stringField(receipt, 'contract_fingerprint'),
559
+ currentStateHash: stringField(receipt, 'head_tree_hash') ??
560
+ stringField(receipt, 'changed_files_hash') ??
561
+ stringField(receipt, 'changed_file_hash'),
562
+ writeDriftStatus: stringField(receipt, 'write_drift_status'),
486
563
  }))
487
564
  : [
488
565
  {
@@ -494,6 +571,10 @@ function createVerificationEvidenceIndex(projectRoot) {
494
571
  verificationPlanId: null,
495
572
  receiptPath: stringField(latest, 'receipt_path') ?? LATEST_RUN_STATE_RELATIVE_PATH,
496
573
  receiptSha256: sourceHash,
574
+ commandFingerprint: stringField(recordField(latest, 'performance'), 'command_fingerprint'),
575
+ contractFingerprint: stringField(recordField(latest, 'performance'), 'contract_fingerprint'),
576
+ currentStateHash: stringField(latest, 'head_tree_hash') ?? stringField(latest, 'changed_files_hash'),
577
+ writeDriftStatus: stringField(recordField(latest, 'write_drift'), 'status'),
497
578
  },
498
579
  ];
499
580
  const coverageStates = rawCoverage.map((coverage) => {
@@ -517,19 +598,146 @@ function createVerificationEvidenceIndex(projectRoot) {
517
598
  severity: stringField(risk, 'severity') ?? 'unknown',
518
599
  detailHash: sha256Text(stringField(risk, 'detail') ?? ''),
519
600
  }));
601
+ const validationRatchetSignals = rawRisks
602
+ .map((risk, index) => {
603
+ const code = stringField(risk, 'code') ?? 'unknown';
604
+ if (!VALIDATION_RATCHET_RISK_CODES.has(code)) {
605
+ return null;
606
+ }
607
+ const severity = stringField(risk, 'severity') ?? 'unknown';
608
+ const pathValue = stringField(risk, 'path');
609
+ const detailHash = sha256Text(stringField(risk, 'detail') ?? '');
610
+ const pathHash = pathValue === null ? hashJson({ code, detailHash }) : sha256Text(pathValue);
611
+ const beforeHash = stringField(risk, 'before_hash') ?? stringField(risk, 'before_digest');
612
+ const afterHash = stringField(risk, 'after_hash') ?? stringField(risk, 'after_digest');
613
+ return {
614
+ signalId: hashJson({
615
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
616
+ ordinal: index + 1,
617
+ planId: verificationPlanId,
618
+ code,
619
+ pathHash,
620
+ beforeHash,
621
+ afterHash,
622
+ }),
623
+ planId: verificationPlanId,
624
+ code,
625
+ severity,
626
+ pathHash,
627
+ beforeHash,
628
+ afterHash,
629
+ };
630
+ })
631
+ .filter((signal) => signal !== null);
632
+ const verificationPlans = verificationPlanId === null
633
+ ? []
634
+ : [
635
+ {
636
+ planId: verificationPlanId,
637
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
638
+ classificationHash: rawRequirements.length > 0 || rawCoverage.length > 0
639
+ ? hashJson({
640
+ requirements: rawRequirements.map((requirement) => ({
641
+ id: stringField(requirement, 'requirement_id') ?? stringField(requirement, 'id'),
642
+ reason: stringField(requirement, 'reason'),
643
+ source: stringField(requirement, 'source'),
644
+ })),
645
+ coverage: rawCoverage.map((coverage) => ({
646
+ id: stringField(coverage, 'criterion_id'),
647
+ reason: stringField(coverage, 'requirement_reason'),
648
+ source: stringField(coverage, 'source'),
649
+ status: stringField(coverage, 'status'),
650
+ })),
651
+ })
652
+ : null,
653
+ commandContractHash: stringListHash(receipts.map((receipt) => receipt.contractFingerprint)),
654
+ selectedIntentsHash: stringListHash(receipts.map((receipt) => receipt.intent)),
655
+ createdAt: stringField(latest, 'started_at') ?? stringField(latest, 'created_at'),
656
+ sourceHash,
657
+ },
658
+ ];
659
+ const acceptanceCriteria = verificationPlanId === null
660
+ ? []
661
+ : rawCoverage.map((coverage) => {
662
+ const evidence = recordField(coverage, 'evidence');
663
+ const pathRefs = [
664
+ ...stringArrayField(evidence, 'paths'),
665
+ ...stringArrayField(evidence, 'changed_paths'),
666
+ ...stringArrayField(evidence, 'source_anchor_ids'),
667
+ ];
668
+ return {
669
+ criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
670
+ planId: verificationPlanId,
671
+ source: stringField(coverage, 'source') ?? 'unknown',
672
+ statementHash: stringField(coverage, 'statement') ? sha256Text(stringField(coverage, 'statement') ?? '') : null,
673
+ reason: stringField(coverage, 'requirement_reason'),
674
+ surface: stringField(coverage, 'surface'),
675
+ pathHash: pathRefs.length > 0 ? stringListHash(pathRefs) : null,
676
+ };
677
+ });
678
+ const criterionCoverage = verificationPlanId === null
679
+ ? []
680
+ : coverageStates.map((coverage) => ({
681
+ criterionId: coverage.criterionId,
682
+ planId: verificationPlanId,
683
+ status: coverage.status,
684
+ receiptCount: coverage.receiptCount,
685
+ gapCount: coverage.gapCount,
686
+ riskCount: coverage.sourceAnchorCount,
687
+ }));
688
+ const commandReceiptSummaries = verificationPlanId === null
689
+ ? []
690
+ : receipts
691
+ .filter((receipt) => receipt.verificationPlanId === verificationPlanId || receipt.verificationPlanId === null)
692
+ .map((receipt) => ({
693
+ receiptHash: receipt.receiptSha256 ??
694
+ hashJson({
695
+ sourcePath: receipt.sourcePath,
696
+ ordinal: receipt.ordinal,
697
+ intent: receipt.intent,
698
+ status: receipt.status,
699
+ verificationPlanId,
700
+ }),
701
+ planId: verificationPlanId,
702
+ intent: receipt.intent,
703
+ status: receipt.status,
704
+ commandFingerprint: receipt.commandFingerprint,
705
+ contractFingerprint: receipt.contractFingerprint,
706
+ currentStateHash: receipt.currentStateHash,
707
+ writeDriftStatus: receipt.writeDriftStatus,
708
+ }));
709
+ const completionVerdictSummaries = verificationPlanId === null || completionStatus === null
710
+ ? []
711
+ : [
712
+ {
713
+ claimId: hashJson({
714
+ sourceHash,
715
+ verificationPlanId,
716
+ completionStatus,
717
+ primaryReason,
718
+ }),
719
+ planId: verificationPlanId,
720
+ status: completionStatus,
721
+ primaryReason,
722
+ riskCount: riskSignals.length,
723
+ contradictionCount: stringArrayField(completionVerdict, 'contradictions').length,
724
+ blockerCount: stringArrayField(completionVerdict, 'blockers').length,
725
+ },
726
+ ];
520
727
  const failedIntents = failedIntentsFromReceipts(receipts);
521
- const failureFingerprint = createFailureFingerprint({
522
- command,
523
- status: completionStatus ?? status,
524
- verificationPlanId,
525
- primaryReason,
526
- failedIntents,
527
- riskCodes: riskSignals.map((risk) => risk.code),
528
- runIntent: stringField(latest, 'intent'),
529
- timedOut: booleanField(latest, 'timed_out'),
530
- exitCodeClass: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'exit_code_class'),
531
- errorKind: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'error_kind'),
532
- });
728
+ const failureFingerprint = recordedFailureFingerprint ??
729
+ createFailureFingerprint({
730
+ command,
731
+ status: completionStatus ?? status,
732
+ verificationPlanId,
733
+ primaryReason,
734
+ failedIntents,
735
+ riskCodes: riskSignals.map((risk) => risk.code),
736
+ runIntent: stringField(latest, 'intent'),
737
+ timedOut: booleanField(latest, 'timed_out'),
738
+ exitCodeClass: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'exit_code_class'),
739
+ errorKind: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'error_kind'),
740
+ });
533
741
  const failureFingerprints = failureFingerprint === null
534
742
  ? []
535
743
  : [
@@ -540,8 +748,50 @@ function createVerificationEvidenceIndex(projectRoot) {
540
748
  status: completionStatus ?? status,
541
749
  failedIntents,
542
750
  primaryReason,
751
+ failedIntentsHash: stringField(recordedFailureFingerprintRecord, 'failed_intents_hash') ??
752
+ stringField(repeatedFailureSummary, 'failed_intents_hash'),
753
+ riskCodesHash: stringField(recordedFailureFingerprintRecord, 'risk_codes_hash') ??
754
+ stringField(repeatedFailureSummary, 'risk_codes_hash'),
755
+ affectedSurfacesHash: stringField(recordedFailureFingerprintRecord, 'affected_surfaces_hash') ??
756
+ stringField(repeatedFailureSummary, 'affected_surfaces_hash'),
757
+ firstSeenAt: stringField(repeatedFailureSummary, 'first_seen_at'),
758
+ lastSeenAt: stringField(repeatedFailureSummary, 'last_seen_at'),
759
+ seenCount: Math.max(1, numberField(repeatedFailureSummary, 'seen_count')),
760
+ requiresNewEvidence: booleanField(repeatedFailureSummary, 'requires_new_evidence'),
543
761
  },
544
762
  ];
763
+ const routeId = stringField(reproductionRoute, 'route_id');
764
+ const reproRoutes = routeId === null || reproEvidence === null
765
+ ? []
766
+ : [
767
+ {
768
+ routeId,
769
+ taskHash: hashJson({
770
+ reported_symptom: stringField(reproEvidence, 'reported_symptom'),
771
+ expected_behavior: stringField(reproEvidence, 'expected_behavior'),
772
+ observed_behavior: stringField(reproEvidence, 'observed_behavior'),
773
+ }),
774
+ routeDigest: stringField(reproductionRoute, 'route_digest'),
775
+ routeKind: stringField(reproductionRoute, 'route_kind'),
776
+ failureOracleHash: stringField(reproductionRoute, 'failure_oracle_hash'),
777
+ },
778
+ ];
779
+ const reproObservations = routeId === null || reproEvidence === null
780
+ ? []
781
+ : [
782
+ reproObservation(routeId, 'before_fix', recordField(reproEvidence, 'before_fix')),
783
+ reproObservation(routeId, 'after_fix', recordField(reproEvidence, 'after_fix')),
784
+ reproObservation(routeId, 'regression_guard', recordField(reproEvidence, 'regression_guard')),
785
+ ];
786
+ const failureFingerprintReadModels = failureFingerprints.map((fingerprint) => ({
787
+ fingerprint: fingerprint.fingerprint,
788
+ planId: fingerprint.verificationPlanId,
789
+ failedIntentsHash: fingerprint.failedIntentsHash ?? stringListHash(fingerprint.failedIntents),
790
+ riskCodesHash: fingerprint.riskCodesHash,
791
+ seenCount: fingerprint.seenCount,
792
+ firstSeenAt: fingerprint.firstSeenAt,
793
+ lastSeenAt: fingerprint.lastSeenAt,
794
+ }));
545
795
  return {
546
796
  summaries: [
547
797
  {
@@ -566,10 +816,19 @@ function createVerificationEvidenceIndex(projectRoot) {
566
816
  failureFingerprint,
567
817
  },
568
818
  ],
819
+ verificationPlans,
820
+ acceptanceCriteria,
821
+ criterionCoverage,
569
822
  receipts,
823
+ commandReceiptSummaries,
570
824
  coverageStates,
571
825
  riskSignals,
826
+ validationRatchetSignals,
827
+ completionVerdictSummaries,
572
828
  failureFingerprints,
829
+ reproRoutes,
830
+ reproObservations,
831
+ failureFingerprintReadModels,
573
832
  };
574
833
  }
575
834
  function readMetadataValue(database, key) {
@@ -1032,6 +1291,37 @@ CREATE TABLE verification_evidence_summaries (
1032
1291
  failure_fingerprint TEXT
1033
1292
  );
1034
1293
 
1294
+ CREATE TABLE verification_plans (
1295
+ plan_id TEXT PRIMARY KEY,
1296
+ source_path TEXT NOT NULL,
1297
+ classification_hash TEXT,
1298
+ command_contract_hash TEXT,
1299
+ selected_intents_hash TEXT,
1300
+ created_at TEXT,
1301
+ source_hash TEXT NOT NULL
1302
+ );
1303
+
1304
+ CREATE TABLE acceptance_criteria (
1305
+ criterion_id TEXT NOT NULL,
1306
+ plan_id TEXT NOT NULL,
1307
+ source TEXT NOT NULL,
1308
+ statement_hash TEXT,
1309
+ reason TEXT,
1310
+ surface TEXT,
1311
+ path_hash TEXT,
1312
+ PRIMARY KEY (plan_id, criterion_id)
1313
+ );
1314
+
1315
+ CREATE TABLE criterion_coverage (
1316
+ criterion_id TEXT NOT NULL,
1317
+ plan_id TEXT NOT NULL,
1318
+ status TEXT NOT NULL,
1319
+ receipt_count INTEGER NOT NULL,
1320
+ gap_count INTEGER NOT NULL,
1321
+ risk_count INTEGER NOT NULL,
1322
+ PRIMARY KEY (plan_id, criterion_id)
1323
+ );
1324
+
1035
1325
  CREATE TABLE verification_receipt_summaries (
1036
1326
  source_path TEXT NOT NULL,
1037
1327
  ordinal INTEGER NOT NULL,
@@ -1044,6 +1334,18 @@ CREATE TABLE verification_receipt_summaries (
1044
1334
  PRIMARY KEY (source_path, ordinal)
1045
1335
  );
1046
1336
 
1337
+ CREATE TABLE command_receipt_summaries (
1338
+ receipt_hash TEXT NOT NULL,
1339
+ plan_id TEXT NOT NULL,
1340
+ intent TEXT,
1341
+ status TEXT NOT NULL,
1342
+ command_fingerprint TEXT,
1343
+ contract_fingerprint TEXT,
1344
+ current_state_hash TEXT,
1345
+ write_drift_status TEXT,
1346
+ PRIMARY KEY (plan_id, receipt_hash)
1347
+ );
1348
+
1047
1349
  CREATE TABLE verification_coverage_states (
1048
1350
  source_path TEXT NOT NULL,
1049
1351
  criterion_id TEXT NOT NULL,
@@ -1066,6 +1368,53 @@ CREATE TABLE verification_risk_signals (
1066
1368
  PRIMARY KEY (source_path, ordinal)
1067
1369
  );
1068
1370
 
1371
+ CREATE TABLE validation_ratchet_signals (
1372
+ signal_id TEXT PRIMARY KEY,
1373
+ plan_id TEXT,
1374
+ code TEXT NOT NULL,
1375
+ severity TEXT NOT NULL,
1376
+ path_hash TEXT NOT NULL,
1377
+ before_hash TEXT,
1378
+ after_hash TEXT
1379
+ );
1380
+
1381
+ CREATE TABLE completion_verdict_summaries (
1382
+ claim_id TEXT PRIMARY KEY,
1383
+ plan_id TEXT NOT NULL,
1384
+ status TEXT NOT NULL,
1385
+ primary_reason TEXT,
1386
+ risk_count INTEGER NOT NULL,
1387
+ contradiction_count INTEGER NOT NULL,
1388
+ blocker_count INTEGER NOT NULL
1389
+ );
1390
+
1391
+ CREATE TABLE repro_routes (
1392
+ route_id TEXT PRIMARY KEY,
1393
+ task_hash TEXT NOT NULL,
1394
+ route_digest TEXT,
1395
+ route_kind TEXT,
1396
+ failure_oracle_hash TEXT
1397
+ );
1398
+
1399
+ CREATE TABLE repro_observations (
1400
+ route_id TEXT NOT NULL,
1401
+ phase TEXT NOT NULL,
1402
+ outcome TEXT,
1403
+ receipt_hash TEXT,
1404
+ diagnostic_fingerprint TEXT NOT NULL,
1405
+ PRIMARY KEY (route_id, phase)
1406
+ );
1407
+
1408
+ CREATE TABLE failure_fingerprints (
1409
+ fingerprint TEXT PRIMARY KEY,
1410
+ plan_id TEXT,
1411
+ failed_intents_hash TEXT,
1412
+ risk_codes_hash TEXT,
1413
+ seen_count INTEGER NOT NULL,
1414
+ first_seen_at TEXT,
1415
+ last_seen_at TEXT
1416
+ );
1417
+
1069
1418
  CREATE TABLE verification_failure_fingerprints (
1070
1419
  source_path TEXT NOT NULL,
1071
1420
  fingerprint TEXT NOT NULL,
@@ -1073,8 +1422,25 @@ CREATE TABLE verification_failure_fingerprints (
1073
1422
  status TEXT NOT NULL,
1074
1423
  failed_intents TEXT NOT NULL,
1075
1424
  primary_reason TEXT,
1425
+ failed_intents_hash TEXT,
1426
+ risk_codes_hash TEXT,
1427
+ affected_surfaces_hash TEXT,
1428
+ first_seen_at TEXT,
1429
+ last_seen_at TEXT,
1430
+ seen_count INTEGER NOT NULL,
1431
+ requires_new_evidence INTEGER NOT NULL,
1076
1432
  PRIMARY KEY (source_path, fingerprint)
1077
1433
  );
1434
+
1435
+ CREATE TABLE source_anchor_risk_signals (
1436
+ anchor_id TEXT PRIMARY KEY,
1437
+ path_hash TEXT NOT NULL,
1438
+ status TEXT NOT NULL,
1439
+ risk_signal TEXT NOT NULL,
1440
+ confidence REAL NOT NULL,
1441
+ navigation_only INTEGER NOT NULL,
1442
+ can_instruct_agent INTEGER NOT NULL
1443
+ );
1078
1444
  `);
1079
1445
  if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1080
1446
  database.run(`
@@ -1258,7 +1624,21 @@ function populateSearchTables(database, capabilities, documents, skills, skillRo
1258
1624
  }
1259
1625
  }
1260
1626
  }
1627
+ function createSourceAnchorRiskSignals(sourceAnchors) {
1628
+ return sourceAnchors
1629
+ .filter((anchor) => ['changed', 'review', 'stale'].includes(anchor.status))
1630
+ .map((anchor) => ({
1631
+ anchorId: anchor.id,
1632
+ pathHash: sha256Text(anchor.path),
1633
+ status: anchor.status,
1634
+ riskSignal: anchor.signals.risk,
1635
+ confidence: anchor.confidence,
1636
+ navigationOnly: anchor.navigationOnly,
1637
+ canInstructAgent: anchor.canInstructAgent,
1638
+ }));
1639
+ }
1261
1640
  function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
1641
+ const sourceAnchorRiskSignals = createSourceAnchorRiskSignals(sourceAnchors);
1262
1642
  database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['schema_version', LOCAL_INDEX_SCHEMA_VERSION]);
1263
1643
  database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['parser_version', LOCAL_INDEX_PARSER_VERSION]);
1264
1644
  database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['content_mode', LOCAL_INDEX_CONTENT_MODE]);
@@ -1418,6 +1798,31 @@ function populateDatabase(database, capabilities, documents, skills, skillRoutes
1418
1798
  summary.failureFingerprint,
1419
1799
  ]);
1420
1800
  }
1801
+ for (const plan of verificationEvidence.verificationPlans) {
1802
+ database.run('INSERT INTO verification_plans (plan_id, source_path, classification_hash, command_contract_hash, selected_intents_hash, created_at, source_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1803
+ plan.planId,
1804
+ plan.sourcePath,
1805
+ plan.classificationHash,
1806
+ plan.commandContractHash,
1807
+ plan.selectedIntentsHash,
1808
+ plan.createdAt,
1809
+ plan.sourceHash,
1810
+ ]);
1811
+ }
1812
+ for (const criterion of verificationEvidence.acceptanceCriteria) {
1813
+ database.run('INSERT INTO acceptance_criteria (criterion_id, plan_id, source, statement_hash, reason, surface, path_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1814
+ criterion.criterionId,
1815
+ criterion.planId,
1816
+ criterion.source,
1817
+ criterion.statementHash,
1818
+ criterion.reason,
1819
+ criterion.surface,
1820
+ criterion.pathHash,
1821
+ ]);
1822
+ }
1823
+ for (const coverage of verificationEvidence.criterionCoverage) {
1824
+ database.run('INSERT INTO criterion_coverage (criterion_id, plan_id, status, receipt_count, gap_count, risk_count) VALUES (?, ?, ?, ?, ?, ?)', [coverage.criterionId, coverage.planId, coverage.status, coverage.receiptCount, coverage.gapCount, coverage.riskCount]);
1825
+ }
1421
1826
  for (const receipt of verificationEvidence.receipts) {
1422
1827
  database.run('INSERT INTO verification_receipt_summaries (source_path, ordinal, intent, status, skipped, verification_plan_id, receipt_path, receipt_sha256) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1423
1828
  receipt.sourcePath,
@@ -1430,6 +1835,18 @@ function populateDatabase(database, capabilities, documents, skills, skillRoutes
1430
1835
  receipt.receiptSha256,
1431
1836
  ]);
1432
1837
  }
1838
+ for (const receipt of verificationEvidence.commandReceiptSummaries) {
1839
+ database.run('INSERT INTO command_receipt_summaries (receipt_hash, plan_id, intent, status, command_fingerprint, contract_fingerprint, current_state_hash, write_drift_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1840
+ receipt.receiptHash,
1841
+ receipt.planId,
1842
+ receipt.intent,
1843
+ receipt.status,
1844
+ receipt.commandFingerprint,
1845
+ receipt.contractFingerprint,
1846
+ receipt.currentStateHash,
1847
+ receipt.writeDriftStatus,
1848
+ ]);
1849
+ }
1433
1850
  for (const coverage of verificationEvidence.coverageStates) {
1434
1851
  database.run('INSERT INTO verification_coverage_states (source_path, criterion_id, source, status, requirement_reason, intents, receipt_count, gap_count, source_anchor_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1435
1852
  coverage.sourcePath,
@@ -1446,14 +1863,69 @@ function populateDatabase(database, capabilities, documents, skills, skillRoutes
1446
1863
  for (const risk of verificationEvidence.riskSignals) {
1447
1864
  database.run('INSERT INTO verification_risk_signals (source_path, ordinal, code, severity, detail_hash) VALUES (?, ?, ?, ?, ?)', [risk.sourcePath, risk.ordinal, risk.code, risk.severity, risk.detailHash]);
1448
1865
  }
1866
+ for (const signal of verificationEvidence.validationRatchetSignals) {
1867
+ database.run('INSERT INTO validation_ratchet_signals (signal_id, plan_id, code, severity, path_hash, before_hash, after_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [signal.signalId, signal.planId, signal.code, signal.severity, signal.pathHash, signal.beforeHash, signal.afterHash]);
1868
+ }
1869
+ for (const verdict of verificationEvidence.completionVerdictSummaries) {
1870
+ database.run('INSERT INTO completion_verdict_summaries (claim_id, plan_id, status, primary_reason, risk_count, contradiction_count, blocker_count) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1871
+ verdict.claimId,
1872
+ verdict.planId,
1873
+ verdict.status,
1874
+ verdict.primaryReason,
1875
+ verdict.riskCount,
1876
+ verdict.contradictionCount,
1877
+ verdict.blockerCount,
1878
+ ]);
1879
+ }
1880
+ for (const route of verificationEvidence.reproRoutes) {
1881
+ database.run('INSERT INTO repro_routes (route_id, task_hash, route_digest, route_kind, failure_oracle_hash) VALUES (?, ?, ?, ?, ?)', [route.routeId, route.taskHash, route.routeDigest, route.routeKind, route.failureOracleHash]);
1882
+ }
1883
+ for (const observation of verificationEvidence.reproObservations) {
1884
+ database.run('INSERT INTO repro_observations (route_id, phase, outcome, receipt_hash, diagnostic_fingerprint) VALUES (?, ?, ?, ?, ?)', [
1885
+ observation.routeId,
1886
+ observation.phase,
1887
+ observation.outcome,
1888
+ observation.receiptHash,
1889
+ observation.diagnosticFingerprint,
1890
+ ]);
1891
+ }
1892
+ for (const fingerprint of verificationEvidence.failureFingerprintReadModels) {
1893
+ database.run('INSERT INTO failure_fingerprints (fingerprint, plan_id, failed_intents_hash, risk_codes_hash, seen_count, first_seen_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1894
+ fingerprint.fingerprint,
1895
+ fingerprint.planId,
1896
+ fingerprint.failedIntentsHash,
1897
+ fingerprint.riskCodesHash,
1898
+ fingerprint.seenCount,
1899
+ fingerprint.firstSeenAt,
1900
+ fingerprint.lastSeenAt,
1901
+ ]);
1902
+ }
1449
1903
  for (const fingerprint of verificationEvidence.failureFingerprints) {
1450
- database.run('INSERT INTO verification_failure_fingerprints (source_path, fingerprint, verification_plan_id, status, failed_intents, primary_reason) VALUES (?, ?, ?, ?, ?, ?)', [
1904
+ database.run('INSERT INTO verification_failure_fingerprints (source_path, fingerprint, verification_plan_id, status, failed_intents, primary_reason, failed_intents_hash, risk_codes_hash, affected_surfaces_hash, first_seen_at, last_seen_at, seen_count, requires_new_evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1451
1905
  fingerprint.sourcePath,
1452
1906
  fingerprint.fingerprint,
1453
1907
  fingerprint.verificationPlanId,
1454
1908
  fingerprint.status,
1455
1909
  joinedList(fingerprint.failedIntents),
1456
1910
  fingerprint.primaryReason,
1911
+ fingerprint.failedIntentsHash,
1912
+ fingerprint.riskCodesHash,
1913
+ fingerprint.affectedSurfacesHash,
1914
+ fingerprint.firstSeenAt,
1915
+ fingerprint.lastSeenAt,
1916
+ fingerprint.seenCount,
1917
+ fingerprint.requiresNewEvidence ? 1 : 0,
1918
+ ]);
1919
+ }
1920
+ for (const signal of sourceAnchorRiskSignals) {
1921
+ database.run('INSERT INTO source_anchor_risk_signals (anchor_id, path_hash, status, risk_signal, confidence, navigation_only, can_instruct_agent) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1922
+ signal.anchorId,
1923
+ signal.pathHash,
1924
+ signal.status,
1925
+ signal.riskSignal,
1926
+ signal.confidence,
1927
+ signal.navigationOnly ? 1 : 0,
1928
+ signal.canInstructAgent ? 1 : 0,
1457
1929
  ]);
1458
1930
  }
1459
1931
  populatePathSurfaceReadModel(database);
@@ -1582,12 +2054,21 @@ export async function createLocalIndex(projectRoot, options = {}) {
1582
2054
  command_intent_count: commandIntents.length,
1583
2055
  command_effect_count: commandIntents.reduce((count, intent) => count + intent.effects.length, 0),
1584
2056
  verification_evidence_summary_count: verificationEvidence.summaries.length,
2057
+ verification_plan_count: verificationEvidence.verificationPlans.length,
2058
+ acceptance_criteria_count: verificationEvidence.acceptanceCriteria.length,
2059
+ criterion_coverage_count: verificationEvidence.criterionCoverage.length,
1585
2060
  verification_receipt_summary_count: verificationEvidence.receipts.length,
2061
+ command_receipt_summary_count: verificationEvidence.commandReceiptSummaries.length,
1586
2062
  verification_coverage_state_count: verificationEvidence.coverageStates.length,
1587
2063
  verification_risk_signal_count: verificationEvidence.riskSignals.length,
2064
+ validation_ratchet_signal_count: verificationEvidence.validationRatchetSignals.length,
2065
+ completion_verdict_summary_count: verificationEvidence.completionVerdictSummaries.length,
2066
+ repro_route_count: verificationEvidence.reproRoutes.length,
2067
+ repro_observation_count: verificationEvidence.reproObservations.length,
1588
2068
  failure_fingerprint_count: verificationEvidence.failureFingerprints.length,
1589
2069
  source_index_enabled: includeSource,
1590
2070
  source_anchor_count: sourceAnchors.length,
2071
+ source_anchor_risk_signal_count: createSourceAnchorRiskSignals(sourceAnchors).length,
1591
2072
  search_backend: capabilities.backend,
1592
2073
  search_fts5_available: capabilities.fts5Available,
1593
2074
  content_mode: LOCAL_INDEX_CONTENT_MODE,
@@ -1786,6 +2267,220 @@ export async function readLocalIndexPromptContext(projectRoot) {
1786
2267
  database?.close();
1787
2268
  }
1788
2269
  }
2270
+ function createVerificationReadModelQueryStatus(databasePath, status, planId, stalePaths = []) {
2271
+ return {
2272
+ source: 'local_index',
2273
+ authority: 'evidence_only',
2274
+ commandAuthority: '.mustflow/config/commands.toml',
2275
+ grantsCommandAuthority: false,
2276
+ status,
2277
+ databasePath,
2278
+ indexFresh: status === 'fresh',
2279
+ stalePaths,
2280
+ planId,
2281
+ uncoveredCriteria: [],
2282
+ severeRisks: [],
2283
+ nonPassingReceipts: [],
2284
+ repeatedFailureFingerprints: [],
2285
+ validationWeakeningSignals: [],
2286
+ refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh verification read-model evidence.',
2287
+ };
2288
+ }
2289
+ function readLatestVerificationPlanId(database) {
2290
+ const row = queryRows(database, `
2291
+ SELECT plan_id
2292
+ FROM verification_plans
2293
+ ORDER BY COALESCE(created_at, '') DESC, source_path DESC, plan_id DESC
2294
+ LIMIT 1
2295
+ `)[0];
2296
+ return toSearchString(row?.plan_id) || null;
2297
+ }
2298
+ function readUncoveredCriteria(database, planId) {
2299
+ return queryRows(database, `
2300
+ SELECT
2301
+ acceptance_criteria.criterion_id,
2302
+ acceptance_criteria.source,
2303
+ acceptance_criteria.reason,
2304
+ acceptance_criteria.surface,
2305
+ acceptance_criteria.path_hash,
2306
+ criterion_coverage.status AS coverage_status,
2307
+ criterion_coverage.receipt_count,
2308
+ criterion_coverage.gap_count,
2309
+ criterion_coverage.risk_count
2310
+ FROM acceptance_criteria
2311
+ LEFT JOIN criterion_coverage
2312
+ ON criterion_coverage.plan_id = acceptance_criteria.plan_id
2313
+ AND criterion_coverage.criterion_id = acceptance_criteria.criterion_id
2314
+ WHERE acceptance_criteria.plan_id = ?
2315
+ AND (criterion_coverage.status IS NULL OR criterion_coverage.status != 'covered')
2316
+ ORDER BY acceptance_criteria.criterion_id
2317
+ `, [planId]).map((row) => ({
2318
+ criterionId: toSearchString(row.criterion_id),
2319
+ source: toSearchString(row.source),
2320
+ reason: toSearchString(row.reason) || null,
2321
+ surface: toSearchString(row.surface) || null,
2322
+ pathHash: toSearchString(row.path_hash) || null,
2323
+ coverageStatus: toSearchString(row.coverage_status) || null,
2324
+ receiptCount: toNullableNumber(row.receipt_count) ?? 0,
2325
+ gapCount: toNullableNumber(row.gap_count) ?? 0,
2326
+ riskCount: toNullableNumber(row.risk_count) ?? 0,
2327
+ }));
2328
+ }
2329
+ function readSevereVerificationRisks(database, planId) {
2330
+ return queryRows(database, `
2331
+ SELECT
2332
+ verification_risk_signals.source_path,
2333
+ verification_risk_signals.ordinal,
2334
+ verification_risk_signals.code,
2335
+ verification_risk_signals.severity,
2336
+ verification_risk_signals.detail_hash
2337
+ FROM verification_risk_signals
2338
+ JOIN verification_evidence_summaries
2339
+ ON verification_evidence_summaries.source_path = verification_risk_signals.source_path
2340
+ WHERE verification_evidence_summaries.verification_plan_id = ?
2341
+ AND verification_risk_signals.severity IN ('high', 'critical')
2342
+ ORDER BY verification_risk_signals.source_path, verification_risk_signals.ordinal
2343
+ `, [planId]).map((row) => ({
2344
+ sourcePath: toSearchString(row.source_path),
2345
+ ordinal: toNullableNumber(row.ordinal) ?? 0,
2346
+ code: toSearchString(row.code),
2347
+ severity: toSearchString(row.severity),
2348
+ detailHash: toSearchString(row.detail_hash),
2349
+ }));
2350
+ }
2351
+ function readNonPassingReceipts(database, planId) {
2352
+ return queryRows(database, `
2353
+ SELECT
2354
+ receipt_hash,
2355
+ plan_id,
2356
+ intent,
2357
+ status,
2358
+ command_fingerprint,
2359
+ contract_fingerprint,
2360
+ current_state_hash,
2361
+ write_drift_status
2362
+ FROM command_receipt_summaries
2363
+ WHERE plan_id = ?
2364
+ AND (
2365
+ status != 'passed'
2366
+ OR write_drift_status IS NULL
2367
+ OR write_drift_status NOT IN ('clean', 'none')
2368
+ )
2369
+ ORDER BY intent, receipt_hash
2370
+ `, [planId]).map((row) => ({
2371
+ receiptHash: toSearchString(row.receipt_hash),
2372
+ planId: toSearchString(row.plan_id),
2373
+ intent: toSearchString(row.intent) || null,
2374
+ status: toSearchString(row.status),
2375
+ commandFingerprint: toSearchString(row.command_fingerprint) || null,
2376
+ contractFingerprint: toSearchString(row.contract_fingerprint) || null,
2377
+ currentStateHash: toSearchString(row.current_state_hash) || null,
2378
+ writeDriftStatus: toSearchString(row.write_drift_status) || null,
2379
+ }));
2380
+ }
2381
+ function readRepeatedFailureFingerprints(database, planId) {
2382
+ return queryRows(database, `
2383
+ SELECT
2384
+ source_path,
2385
+ fingerprint,
2386
+ verification_plan_id,
2387
+ status,
2388
+ failed_intents,
2389
+ primary_reason,
2390
+ failed_intents_hash,
2391
+ risk_codes_hash,
2392
+ affected_surfaces_hash,
2393
+ seen_count,
2394
+ requires_new_evidence
2395
+ FROM verification_failure_fingerprints
2396
+ WHERE verification_plan_id = ?
2397
+ AND requires_new_evidence = 1
2398
+ ORDER BY fingerprint
2399
+ `, [planId]).map((row) => ({
2400
+ sourcePath: toSearchString(row.source_path),
2401
+ fingerprint: toSearchString(row.fingerprint),
2402
+ verificationPlanId: toSearchString(row.verification_plan_id) || null,
2403
+ status: toSearchString(row.status),
2404
+ failedIntents: splitIndexedList(row.failed_intents),
2405
+ primaryReason: toSearchString(row.primary_reason) || null,
2406
+ failedIntentsHash: toSearchString(row.failed_intents_hash) || null,
2407
+ riskCodesHash: toSearchString(row.risk_codes_hash) || null,
2408
+ affectedSurfacesHash: toSearchString(row.affected_surfaces_hash) || null,
2409
+ seenCount: toNullableNumber(row.seen_count) ?? 0,
2410
+ requiresNewEvidence: Number(row.requires_new_evidence) === 1,
2411
+ }));
2412
+ }
2413
+ function readValidationWeakeningSignals(database, planId) {
2414
+ return queryRows(database, `
2415
+ SELECT signal_id, plan_id, code, severity, path_hash, before_hash, after_hash
2416
+ FROM validation_ratchet_signals
2417
+ WHERE plan_id = ?
2418
+ ORDER BY severity DESC, code, signal_id
2419
+ `, [planId]).map((row) => ({
2420
+ signalId: toSearchString(row.signal_id),
2421
+ planId: toSearchString(row.plan_id) || null,
2422
+ code: toSearchString(row.code),
2423
+ severity: toSearchString(row.severity),
2424
+ pathHash: toSearchString(row.path_hash),
2425
+ beforeHash: toSearchString(row.before_hash) || null,
2426
+ afterHash: toSearchString(row.after_hash) || null,
2427
+ }));
2428
+ }
2429
+ function queryLocalVerificationReadModel(databasePath, database, planId) {
2430
+ const selectedPlanId = planId ?? readLatestVerificationPlanId(database);
2431
+ const base = createVerificationReadModelQueryStatus(databasePath, 'fresh', selectedPlanId);
2432
+ if (!selectedPlanId) {
2433
+ return base;
2434
+ }
2435
+ return {
2436
+ ...base,
2437
+ uncoveredCriteria: readUncoveredCriteria(database, selectedPlanId),
2438
+ severeRisks: readSevereVerificationRisks(database, selectedPlanId),
2439
+ nonPassingReceipts: readNonPassingReceipts(database, selectedPlanId),
2440
+ repeatedFailureFingerprints: readRepeatedFailureFingerprints(database, selectedPlanId),
2441
+ validationWeakeningSignals: readValidationWeakeningSignals(database, selectedPlanId),
2442
+ };
2443
+ }
2444
+ export async function readLocalVerificationReadModelQueries(projectRoot, planId) {
2445
+ return readLocalVerificationReadModelQueriesForPlan(projectRoot, planId);
2446
+ }
2447
+ export async function readLocalVerificationReadModelQueriesForPlan(projectRoot, planId) {
2448
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2449
+ if (!existsSync(databasePath)) {
2450
+ return createVerificationReadModelQueryStatus(databasePath, 'missing', planId);
2451
+ }
2452
+ const SQL = await loadSqlJs();
2453
+ const database = new SQL.Database(readFileSync(databasePath));
2454
+ try {
2455
+ const requiredTables = [
2456
+ 'acceptance_criteria',
2457
+ 'criterion_coverage',
2458
+ 'verification_evidence_summaries',
2459
+ 'verification_plans',
2460
+ 'verification_risk_signals',
2461
+ 'command_receipt_summaries',
2462
+ 'verification_failure_fingerprints',
2463
+ 'validation_ratchet_signals',
2464
+ ];
2465
+ if (requiredTables.some((tableName) => !hasTable(database, tableName))) {
2466
+ return createVerificationReadModelQueryStatus(databasePath, 'unreadable', planId);
2467
+ }
2468
+ const stalePaths = getStalePaths(projectRoot, database);
2469
+ if (stalePaths.length > 0) {
2470
+ return createVerificationReadModelQueryStatus(databasePath, 'stale', planId, stalePaths);
2471
+ }
2472
+ return queryLocalVerificationReadModel(databasePath, database, planId);
2473
+ }
2474
+ catch {
2475
+ return createVerificationReadModelQueryStatus(databasePath, 'unreadable', planId);
2476
+ }
2477
+ finally {
2478
+ database.close();
2479
+ }
2480
+ }
2481
+ export async function readLatestLocalVerificationReadModelQueries(projectRoot) {
2482
+ return readLocalVerificationReadModelQueriesForPlan(projectRoot, null);
2483
+ }
1789
2484
  function pathSurfaceReadModelWithMatch(base, match) {
1790
2485
  return {
1791
2486
  ...base,