kibi-core 0.11.0 → 0.12.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.
@@ -4,6 +4,7 @@
4
4
  :- module(requirement_proof, [
5
5
  requirement_proof_context/1,
6
6
  requirement_proof_context/4,
7
+ requirement_proof_context/6,
7
8
  requirement_proof/4
8
9
  ]).
9
10
 
@@ -19,12 +20,23 @@
19
20
  ]).
20
21
  :- use_module('logic_ir.pl', [logic_rule_safety/2]).
21
22
 
22
- proof_version('kibi.requirement-proof.v2').
23
+ proof_version('kibi.requirement-proof.v3').
23
24
 
24
25
  requirement_proof_context(Context) :-
25
26
  requirement_proof_context(unknown, '1970-01-01T00:00:00Z', 604800, Context).
26
27
 
27
28
  requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, Context) :-
29
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
30
+ strict_snapshot, _{}, Context).
31
+
32
+ % Per-contract receipt binding (W2): BindingMode is strict_snapshot (the
33
+ % default: a receipt is current only against the whole-workspace snapshot it
34
+ % was proven on) or per_contract (a receipt carrying a binding_hash is current
35
+ % while its contract + receipt-stripped test document are unchanged, via the
36
+ % TestBindings dict TestId -> BindingHash; receipts without a binding_hash
37
+ % still fall back to the snapshot match).
38
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
39
+ BindingMode, TestBindings, Context) :-
28
40
  check_domain_contradictions_and_witnesses(Contradictions, ContradictionWitnesses),
29
41
  check_rule_safety(UnsafeRules),
30
42
  check_rule_verifiability(UnverifiableRules),
@@ -32,36 +44,41 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, Contex
32
44
  normalize_atom(CheckedAt, CheckedAtAtom),
33
45
  (catch(parse_time(CheckedAtAtom, CheckedAtStamp), _, fail) -> true ; CheckedAtStamp = -1),
34
46
  normalize_integer(MaxAgeSeconds, MaxAge),
47
+ normalize_atom(BindingMode, BindingModeAtom),
35
48
  Context = _{
36
49
  contradictions: Contradictions,
37
50
  contradictionWitnesses: ContradictionWitnesses,
38
51
  unsafeRules: UnsafeRules,
39
52
  unverifiableRules: UnverifiableRules,
40
- verificationSnapshot: Snapshot,
41
- verificationCheckedAt: CheckedAtAtom,
42
- verificationCheckedAtStamp: CheckedAtStamp,
43
- verificationMaxAgeSeconds: MaxAge
53
+ proofSnapshot: Snapshot,
54
+ proofCheckedAt: CheckedAtAtom,
55
+ proofCheckedAtStamp: CheckedAtStamp,
56
+ proofMaxAgeSeconds: MaxAge,
57
+ proofBindingMode: BindingModeAtom,
58
+ proofTestBindings: TestBindings
44
59
  }.
45
60
 
61
+ % implements REQ-kibi-proof-applicability-reason
62
+ % Exemption first: a current requirement explicitly marked proof_exempt with a
63
+ % reason is intentionally outside E2E-proof scope and must never fall through
64
+ % to the ladder (which would report misleading receipt gaps).
65
+ requirement_proof(ReqId, ReqProps, _Context, Proof) :-
66
+ kb:current_req(ReqId),
67
+ proof_exempt(ReqProps, Reason),
68
+ !,
69
+ exempt_requirement_proof(Reason, Proof).
46
70
  requirement_proof(ReqId, _ReqProps, _Context, Proof) :-
47
71
  \+ kb:current_req(ReqId),
48
72
  !,
49
- proof_version(Version),
50
- Proof = _{
51
- proofVersion: Version,
52
- proofStatus: not_applicable,
53
- proofGaps: [],
54
- proofAdvisories: [],
55
- proofRepairs: [],
56
- proofStages: _{applicability: _{status: not_applicable}}
57
- }.
73
+ applicability_exclusion_reason(ReqId, Reason),
74
+ exempt_requirement_proof(Reason, Proof).
58
75
  requirement_proof(ReqId, ReqProps, Context, Proof) :-
59
76
  semantic_inventory_stage(ReqProps, SemanticStage, Inventory),
60
77
  logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
61
78
  contradiction_stage(ReqId, LogicStage.status, Context, ContradictionStage),
62
79
  scenario_stage(ReqId, ScenarioStage, ScenarioIds),
63
- scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
64
- passing_e2e_stage(ScenarioTests, Context, PassingE2eStage, PassingE2eTests),
80
+ scenario_test_stage(ScenarioIds, ScenarioTestStage, _ScenarioTests),
81
+ passing_e2e_stage(ScenarioTestStage, Context, PassingE2eStage, PassingE2eTests),
65
82
  executable_symbol_stage(PassingE2eTests, ExecutableStage, ExecutableSymbols),
66
83
  production_symbol_stage(ReqId, PassingE2eTests, ProductionStage, ProductionSymbols),
67
84
  source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, CoordinateStage),
@@ -89,6 +106,45 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
89
106
  proofStages: Stages
90
107
  }.
91
108
 
109
+ exempt_requirement_proof(Reason, Proof) :-
110
+ proof_version(Version),
111
+ Proof = _{
112
+ proofVersion: Version,
113
+ proofStatus: not_applicable,
114
+ proofGaps: [],
115
+ proofAdvisories: [],
116
+ proofRepairs: [],
117
+ proofStages: _{applicability: _{status: not_applicable, reason: Reason}}
118
+ }.
119
+
120
+ %% proof_exempt(+Props, -Reason)
121
+ % A current requirement is exempt from E2E-proof scope when its author set
122
+ % proof_exempt to a truthy value AND supplied a non-empty proof_exempt_reason.
123
+ % Without the reason the exemption does not apply, so an author cannot park a
124
+ % requirement silently; upsert validation enforces the same pairing.
125
+ proof_exempt(Props, Reason) :-
126
+ memberchk(proof_exempt=RawFlag, Props),
127
+ normalize_atom(RawFlag, Flag),
128
+ memberchk(Flag, [true, yes, on, '1']),
129
+ memberchk(proof_exempt_reason=RawReason, Props),
130
+ normalize_atom(RawReason, Reason),
131
+ Reason \= ''.
132
+
133
+ %% applicability_exclusion_reason(+ReqId, -Reason)
134
+ % Typed reason a requirement fails current_req/1: superseded lifecycle, or a
135
+ % status outside the canonical+legacy requirement vocabulary (e.g. the ADR
136
+ % vocabulary 'accepted').
137
+ applicability_exclusion_reason(ReqId, "requirement is superseded by a newer requirement") :-
138
+ kb_relationship(supersedes, _, ReqId),
139
+ !.
140
+ applicability_exclusion_reason(ReqId, Reason) :-
141
+ kb_entity(ReqId, req, Props),
142
+ memberchk(status=Status, Props),
143
+ normalize_term_atom(Status, StatusAtom),
144
+ format(atom(Reason), "status '~w' is not a current requirement status", [StatusAtom]),
145
+ !.
146
+ applicability_exclusion_reason(_ReqId, "requirement is not current").
147
+
92
148
  semantic_inventory_stage(Props, Stage, Entries) :-
93
149
  ( memberchk(semantic_inventory=RawInventory, Props),
94
150
  inventory_entries(RawInventory, Entries0),
@@ -435,31 +491,94 @@ violation_for_entity(Violations, EntityId) :-
435
491
 
436
492
  scenario_stage(ReqId, Stage, ScenarioIds) :-
437
493
  findall(ScenarioId,
438
- (kb_relationship(specified_by, ReqId, ScenarioId), kb_entity(ScenarioId, scenario, _)),
439
- ScenarioIds0),
440
- sort(ScenarioIds0, ScenarioIds),
441
- (ScenarioIds = [] -> Status = missing ; Status = passed),
494
+ kb_relationship(specified_by, ReqId, ScenarioId),
495
+ ScenarioTargets0),
496
+ sort(ScenarioTargets0, ScenarioTargets),
497
+ include(existing_scenario, ScenarioTargets, ScenarioIds),
498
+ findall(ScenarioId,
499
+ (member(ScenarioId, ScenarioTargets), \+ existing_scenario(ScenarioId)),
500
+ InvalidScenarioTargets),
501
+ ((ScenarioIds = [] ; InvalidScenarioTargets \= []) -> Status = missing ; Status = passed),
442
502
  maplist(entity_source_ref, ScenarioIds, Sources),
443
- Stage = _{status: Status, scenarios: ScenarioIds, sources: Sources}.
503
+ Stage = _{
504
+ status: Status,
505
+ scenarios: ScenarioIds,
506
+ scenarioTargets: ScenarioTargets,
507
+ invalidScenarioTargets: InvalidScenarioTargets,
508
+ sources: Sources
509
+ }.
510
+
511
+ existing_scenario(ScenarioId) :-
512
+ kb_entity(ScenarioId, scenario, _).
444
513
 
445
514
  scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
515
+ maplist(scenario_test_obligation, ScenarioIds, Obligations),
446
516
  findall(TestId,
447
- (member(ScenarioId, ScenarioIds), scenario_test(ScenarioId, TestId)),
517
+ (member(Obligation, Obligations), member(TestId, Obligation.tests)),
448
518
  ScenarioTests0),
449
519
  sort(ScenarioTests0, ScenarioTests),
450
- (ScenarioTests = [] -> Status = missing ; Status = passed),
520
+ findall(TestId,
521
+ (member(Obligation, Obligations), member(TestId, Obligation.scenarioTestTargets)),
522
+ ScenarioTestTargets0),
523
+ sort(ScenarioTestTargets0, ScenarioTestTargets),
524
+ findall(TestId,
525
+ (member(Obligation, Obligations), member(TestId, Obligation.invalidScenarioTestTargets)),
526
+ InvalidScenarioTestTargets0),
527
+ sort(InvalidScenarioTestTargets0, InvalidScenarioTestTargets),
528
+ ( ScenarioIds == []
529
+ -> Status = missing
530
+ ; member(Obligation, Obligations), Obligation.status == missing
531
+ -> Status = missing
532
+ ; Status = passed
533
+ ),
451
534
  maplist(entity_source_ref, ScenarioTests, Sources),
452
- Stage = _{status: Status, tests: ScenarioTests, sources: Sources}.
535
+ Stage = _{
536
+ status: Status,
537
+ scenarios: ScenarioIds,
538
+ tests: ScenarioTests,
539
+ scenarioTestTargets: ScenarioTestTargets,
540
+ invalidScenarioTestTargets: InvalidScenarioTestTargets,
541
+ sources: Sources,
542
+ obligations: Obligations
543
+ }.
453
544
 
454
- scenario_test(ScenarioId, TestId) :-
455
- kb_relationship(verified_by, ScenarioId, TestId),
545
+ scenario_test_obligation(ScenarioId, Obligation) :-
546
+ findall(TestId, declared_scenario_test_target(ScenarioId, TestId), TestTargets0),
547
+ sort(TestTargets0, TestTargets),
548
+ include(existing_test, TestTargets, TestIds),
549
+ findall(TestId,
550
+ (member(TestId, TestTargets), \+ existing_test(TestId)),
551
+ InvalidScenarioTestTargets),
552
+ maplist(entity_source_ref, TestIds, Sources),
553
+ ( (TestIds == [] ; InvalidScenarioTestTargets \= [])
554
+ -> Status = missing
555
+ ; Status = passed
556
+ ),
557
+ Obligation = _{
558
+ scenarioId: ScenarioId,
559
+ status: Status,
560
+ scenarioTestTargets: TestTargets,
561
+ invalidScenarioTestTargets: InvalidScenarioTestTargets,
562
+ tests: TestIds,
563
+ sources: Sources
564
+ }.
565
+
566
+ declared_scenario_test_target(ScenarioId, TestId) :-
567
+ kb_relationship(verified_by, ScenarioId, TestId).
568
+ declared_scenario_test_target(ScenarioId, TestId) :-
569
+ kb_relationship(validates, TestId, ScenarioId).
570
+
571
+ existing_test(TestId) :-
456
572
  kb_entity(TestId, test, _).
573
+
457
574
  scenario_test(ScenarioId, TestId) :-
458
- kb_relationship(validates, TestId, ScenarioId),
459
- kb_entity(TestId, test, _).
575
+ declared_scenario_test_target(ScenarioId, TestId),
576
+ existing_test(TestId).
460
577
 
461
- passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
462
- maplist(test_receipt_evidence(Context), ScenarioTests, Evidence),
578
+ passing_e2e_stage(ScenarioTestStage, Context, Stage, PassingE2eTests) :-
579
+ maplist(scenario_passing_e2e_obligation(Context), ScenarioTestStage.obligations, ScenarioObligations),
580
+ findall(Item, (member(Obligation, ScenarioObligations), member(Item, Obligation.evidence)), Evidence0),
581
+ unique_evidence_by_test(Evidence0, Evidence),
463
582
  evidence_tests_with_state(Evidence, passed, PassingE2eTests),
464
583
  evidence_tests_with_state(Evidence, missing, MissingReceiptTests),
465
584
  evidence_tests_with_state(Evidence, stale, StaleReceiptTests),
@@ -468,10 +587,11 @@ passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
468
587
  evidence_tests_with_state(Evidence, contract_mismatch, ContractMismatchReceiptTests),
469
588
  evidence_tests_with_state(Evidence, snapshot_unavailable, SnapshotUnavailableTests),
470
589
  evidence_tests_with_state(Evidence, not_end_to_end, NonEndToEndTests),
471
- passing_e2e_status(PassingE2eTests, InvalidReceiptTests, ContractMismatchReceiptTests, SnapshotUnavailableTests, Status),
590
+ passing_e2e_status(ScenarioObligations, Status),
472
591
  Stage = _{
473
592
  status: Status,
474
593
  tests: PassingE2eTests,
594
+ scenarioObligations: ScenarioObligations,
475
595
  receiptEvidence: Evidence,
476
596
  missingReceiptTests: MissingReceiptTests,
477
597
  staleReceiptTests: StaleReceiptTests,
@@ -480,16 +600,104 @@ passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
480
600
  contractMismatchReceiptTests: ContractMismatchReceiptTests,
481
601
  snapshotUnavailableTests: SnapshotUnavailableTests,
482
602
  nonEndToEndTests: NonEndToEndTests,
483
- currentCodeSnapshot: Context.verificationSnapshot,
484
- checkedAt: Context.verificationCheckedAt,
485
- maxAgeSeconds: Context.verificationMaxAgeSeconds
603
+ currentCodeSnapshot: Context.proofSnapshot,
604
+ checkedAt: Context.proofCheckedAt,
605
+ maxAgeSeconds: Context.proofMaxAgeSeconds
606
+ }.
607
+
608
+ unique_evidence_by_test(Evidence0, Evidence) :-
609
+ findall(TestId,
610
+ (member(Item, Evidence0), TestId = Item.testId),
611
+ TestIds0),
612
+ sort(TestIds0, TestIds),
613
+ maplist(first_evidence_for_test(Evidence0), TestIds, Evidence).
614
+
615
+ first_evidence_for_test(Evidence, TestId, Item) :-
616
+ member(Item, Evidence),
617
+ Item.testId == TestId,
618
+ !.
619
+
620
+ scenario_passing_e2e_obligation(Context, TestObligation, Obligation) :-
621
+ maplist(test_receipt_evidence(Context), TestObligation.tests, Evidence),
622
+ include(e2e_evidence, Evidence, E2eEvidence),
623
+ include(non_e2e_evidence, Evidence, NonEndToEndEvidence),
624
+ evidence_tests_with_state(E2eEvidence, passed, PassingE2eTests),
625
+ evidence_tests_with_state(E2eEvidence, missing, MissingReceiptTests),
626
+ evidence_tests_with_state(E2eEvidence, stale, StaleReceiptTests),
627
+ evidence_tests_with_state(E2eEvidence, failed, FailedReceiptTests),
628
+ evidence_tests_with_state(E2eEvidence, invalid, InvalidReceiptTests),
629
+ evidence_tests_with_state(E2eEvidence, contract_mismatch, ContractMismatchReceiptTests),
630
+ evidence_tests_with_state(E2eEvidence, snapshot_unavailable, SnapshotUnavailableTests),
631
+ evidence_tests_with_state(NonEndToEndEvidence, not_end_to_end, NonEndToEndTests),
632
+ scenario_obligation_gaps(TestObligation, E2eEvidence, Gaps),
633
+ scenario_obligation_status(TestObligation, E2eEvidence, Status),
634
+ findall(TestId,
635
+ (member(Item, E2eEvidence), TestId = Item.testId),
636
+ E2eTests0),
637
+ sort(E2eTests0, E2eTests),
638
+ Obligation = _{
639
+ scenarioId: TestObligation.scenarioId,
640
+ status: Status,
641
+ scenarioTestTargets: TestObligation.scenarioTestTargets,
642
+ invalidScenarioTestTargets: TestObligation.invalidScenarioTestTargets,
643
+ tests: TestObligation.tests,
644
+ e2eTests: E2eTests,
645
+ nonEndToEndTests: NonEndToEndTests,
646
+ evidence: Evidence,
647
+ gaps: Gaps,
648
+ passingE2eTests: PassingE2eTests,
649
+ missingReceiptTests: MissingReceiptTests,
650
+ staleReceiptTests: StaleReceiptTests,
651
+ failedReceiptTests: FailedReceiptTests,
652
+ invalidReceiptTests: InvalidReceiptTests,
653
+ contractMismatchReceiptTests: ContractMismatchReceiptTests,
654
+ snapshotUnavailableTests: SnapshotUnavailableTests
486
655
  }.
487
656
 
488
- passing_e2e_status(Passing, _, _, _, passed) :- Passing \= [], !.
489
- passing_e2e_status([], Invalid, _, _, unresolved) :- Invalid \= [], !.
490
- passing_e2e_status([], _, ContractMismatch, _, unresolved) :- ContractMismatch \= [], !.
491
- passing_e2e_status([], _, _, SnapshotUnavailable, unresolved) :- SnapshotUnavailable \= [], !.
492
- passing_e2e_status([], _, _, _, missing).
657
+ e2e_evidence(Item) :- Item.state \= not_end_to_end.
658
+ non_e2e_evidence(Item) :- Item.state == not_end_to_end.
659
+
660
+ scenario_obligation_gaps(TestObligation, E2eEvidence, Gaps) :-
661
+ findall(Gap, scenario_obligation_gap(TestObligation, E2eEvidence, Gap), Gaps0),
662
+ sort(Gaps0, Gaps).
663
+
664
+ scenario_obligation_gap(TestObligation, _E2eEvidence, missing_scenario_test) :-
665
+ TestObligation.status == missing.
666
+ scenario_obligation_gap(TestObligation, E2eEvidence, missing_passing_e2e) :-
667
+ TestObligation.status == passed,
668
+ E2eEvidence == [].
669
+ scenario_obligation_gap(_TestObligation, Evidence, missing_proof_receipt) :-
670
+ member(Item, Evidence), Item.state == missing.
671
+ scenario_obligation_gap(_TestObligation, Evidence, stale_proof_receipt) :-
672
+ member(Item, Evidence), Item.state == stale.
673
+ scenario_obligation_gap(_TestObligation, Evidence, failed_proof_receipt) :-
674
+ member(Item, Evidence), Item.state == failed.
675
+ scenario_obligation_gap(_TestObligation, Evidence, invalid_proof_receipt) :-
676
+ member(Item, Evidence), Item.state == invalid.
677
+ scenario_obligation_gap(_TestObligation, Evidence, proof_contract_mismatch) :-
678
+ member(Item, Evidence), Item.state == contract_mismatch.
679
+ scenario_obligation_gap(_TestObligation, Evidence, proof_snapshot_unavailable) :-
680
+ member(Item, Evidence), Item.state == snapshot_unavailable.
681
+
682
+ scenario_obligation_status(TestObligation, _E2eEvidence, missing) :-
683
+ TestObligation.status == missing,
684
+ !.
685
+ scenario_obligation_status(_TestObligation, [], missing) :-
686
+ !.
687
+ scenario_obligation_status(_TestObligation, Evidence, passed) :-
688
+ Evidence \= [],
689
+ forall(member(Item, Evidence), Item.state == passed),
690
+ !.
691
+ scenario_obligation_status(_TestObligation, _Evidence, unresolved).
692
+
693
+ passing_e2e_status([], missing) :- !.
694
+ passing_e2e_status(Obligations, passed) :-
695
+ forall(member(Obligation, Obligations), Obligation.status == passed),
696
+ !.
697
+ passing_e2e_status(Obligations, missing) :-
698
+ member(Obligation, Obligations), Obligation.status == missing,
699
+ !.
700
+ passing_e2e_status(_Obligations, unresolved).
493
701
 
494
702
  evidence_tests_with_state(Evidence, State, TestIds) :-
495
703
  findall(TestId,
@@ -502,9 +710,9 @@ test_receipt_evidence(Context, TestId, Evidence) :-
502
710
  test_scope(Props, Scope),
503
711
  ( Scope \= end_to_end
504
712
  -> Evidence = _{testId: TestId, state: not_end_to_end, scope: Scope}
505
- ; Context.verificationSnapshot == unknown
713
+ ; Context.proofSnapshot == unknown
506
714
  -> Evidence = _{testId: TestId, state: snapshot_unavailable, scope: Scope}
507
- ; verification_receipt_entries(Props, Receipts, ReceiptPropertyPresent),
715
+ ; proof_receipt_entries(Props, Receipts, ReceiptPropertyPresent),
508
716
  receipt_evidence_state(TestId, Scope, Props, Receipts, ReceiptPropertyPresent, Context, Evidence)
509
717
  ).
510
718
 
@@ -513,18 +721,18 @@ receipt_evidence_state(TestId, Scope, _Props, [], false, _Context,
513
721
  receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, _Context,
514
722
  _{testId: TestId, state: invalid, scope: Scope, receiptCount: Count}) :-
515
723
  length(Receipts, Count),
516
- verification_contract_binding(Props, ContractBinding),
724
+ proof_contract_binding(Props, ContractBinding),
517
725
  (ContractBinding == invalid
518
726
  ; Receipts == []
519
727
  ; member(Receipt, Receipts), \+ valid_receipt_shape(TestId, Receipt)
520
728
  ; \+ chronological_receipt_history(Receipts)),
521
729
  !.
522
730
  receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Evidence) :-
523
- include(receipt_for_snapshot(Context.verificationSnapshot), Receipts, SnapshotReceipts),
731
+ receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts),
524
732
  ( SnapshotReceipts == []
525
733
  -> length(Receipts, Count),
526
734
  Evidence = _{testId: TestId, state: stale, scope: Scope, receiptCount: Count}
527
- ; verification_contract_binding(Props, ContractBinding),
735
+ ; proof_contract_binding(Props, ContractBinding),
528
736
  include(receipt_matches_current_binding(Scope, ContractBinding), SnapshotReceipts, CurrentReceipts),
529
737
  ( CurrentReceipts == []
530
738
  -> receipt_contract_mismatch_evidence(TestId, Scope, SnapshotReceipts, ContractBinding, Evidence)
@@ -534,8 +742,8 @@ receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Eviden
534
742
  )
535
743
  ).
536
744
 
537
- verification_receipt_entries(Props, Receipts, Present) :-
538
- ( memberchk(verification_receipts=Raw, Props)
745
+ proof_receipt_entries(Props, Receipts, Present) :-
746
+ ( memberchk(proof_receipts=Raw, Props)
539
747
  -> Present = true,
540
748
  inventory_entries(Raw, Receipts)
541
749
  ; Present = false,
@@ -544,16 +752,11 @@ verification_receipt_entries(Props, Receipts, Present) :-
544
752
 
545
753
  valid_receipt_shape(TestId, Receipt) :-
546
754
  inventory_entry_field(Receipt, version, RawVersion),
547
- normalize_receipt_atom(RawVersion, Version),
548
- memberchk(Version, ['kibi.verification-receipt.v1', 'kibi.verification-receipt.v2']),
755
+ normalize_receipt_atom(RawVersion, 'kibi.proof-receipt.v1'),
549
756
  inventory_entry_field(Receipt, receipt_id, RawReceiptId),
550
757
  normalize_receipt_atom(RawReceiptId, ReceiptId), valid_receipt_id(ReceiptId),
551
758
  inventory_entry_field(Receipt, test_id, RawTestId),
552
759
  normalize_receipt_atom(RawTestId, TestId),
553
- inventory_entry_field(Receipt, runner, RawRunner),
554
- normalize_receipt_atom(RawRunner, Runner), Runner \= '',
555
- inventory_entry_field(Receipt, command, RawCommand),
556
- normalize_receipt_atom(RawCommand, Command), Command \= '',
557
760
  inventory_entry_field(Receipt, scope, RawScope),
558
761
  normalize_receipt_atom(RawScope, ReceiptScope),
559
762
  memberchk(ReceiptScope, [unit, integration, end_to_end]),
@@ -566,55 +769,83 @@ valid_receipt_shape(TestId, Receipt) :-
566
769
  normalize_receipt_atom(RawEnvironmentHash, EnvironmentHash), valid_sha256(EnvironmentHash),
567
770
  inventory_entry_field(Receipt, artifact_digest, RawArtifactDigest),
568
771
  normalize_receipt_atom(RawArtifactDigest, ArtifactDigest), valid_sha256(ArtifactDigest),
569
- receipt_timestamp(Receipt, started_at, StartedStamp),
570
- receipt_timestamp(Receipt, finished_at, FinishedStamp),
571
- FinishedStamp >= StartedStamp,
572
- valid_receipt_version_fields(Version, Receipt).
573
-
574
- valid_receipt_version_fields('kibi.verification-receipt.v1', _Receipt).
575
- valid_receipt_version_fields('kibi.verification-receipt.v2', Receipt) :-
772
+ inventory_entry_field(Receipt, contract_hash, RawContractHash),
773
+ normalize_receipt_atom(RawContractHash, ContractHash), valid_sha256(ContractHash),
774
+ inventory_entry_field(Receipt, fingerprint, RawFingerprint),
775
+ normalize_receipt_atom(RawFingerprint, Fingerprint), valid_sha256(Fingerprint),
776
+ valid_fingerprint_components(Receipt),
777
+ inventory_entry_field(Receipt, integration_id, RawIntegrationId),
778
+ normalize_receipt_atom(RawIntegrationId, IntegrationId), IntegrationId \= '',
779
+ inventory_entry_field(Receipt, producer, RawProducer),
780
+ receipt_producer_name(RawProducer, ProducerName), ProducerName \= '',
576
781
  inventory_entry_field(Receipt, command_argv, RawCommandArgv),
577
782
  is_list(RawCommandArgv),
578
783
  RawCommandArgv \= [],
579
784
  maplist(nonempty_receipt_atom, RawCommandArgv),
580
- inventory_entry_field(Receipt, contract_hash, RawContractHash),
581
- normalize_receipt_atom(RawContractHash, ContractHash),
582
- valid_sha256(ContractHash),
583
- inventory_entry_field(Receipt, case_results, RawCases),
584
- is_list(RawCases),
585
- RawCases \= [],
586
- maplist(valid_receipt_case, RawCases),
587
- findall(Key,
588
- (member(Case, RawCases), receipt_case_key(Case, Key)), Keys),
589
- sort(Keys, UniqueKeys),
590
- length(Keys, KeyCount),
591
- length(UniqueKeys, KeyCount).
785
+ ( inventory_entry_field(Receipt, binding_hash, RawBindingHash)
786
+ -> normalize_receipt_atom(RawBindingHash, BindingHash),
787
+ valid_sha256(BindingHash)
788
+ ; true
789
+ ),
790
+ inventory_entry_field(Receipt, run_outcome, RawRunOutcome),
791
+ normalize_receipt_atom(RawRunOutcome, RunOutcome),
792
+ memberchk(RunOutcome, [passed, failed, errored, cancelled, timed_out, interrupted, no_results]),
793
+ inventory_entry_field(Receipt, proof_results, RawResults),
794
+ is_list(RawResults),
795
+ maplist(valid_receipt_proof_result, RawResults),
796
+ receipt_timestamp(Receipt, started_at, StartedStamp),
797
+ receipt_timestamp(Receipt, finished_at, FinishedStamp),
798
+ FinishedStamp >= StartedStamp.
799
+
800
+ receipt_producer_name(Producer, Name) :-
801
+ inventory_entry_field(Producer, name, RawName),
802
+ normalize_receipt_atom(RawName, Name).
803
+
804
+ valid_fingerprint_components(Receipt) :-
805
+ inventory_entry_field(Receipt, fingerprint_components, Components),
806
+ forall(member(Key, [contract, integration, command, bindings, producer]),
807
+ (inventory_entry_field(Components, Key, Raw),
808
+ normalize_receipt_atom(Raw, Value),
809
+ valid_sha256(Value))),
810
+ !.
592
811
 
593
812
  nonempty_receipt_atom(Value) :-
594
813
  normalize_receipt_atom(Value, Atom),
595
814
  Atom \= ''.
596
815
 
597
- valid_receipt_case(Case) :-
598
- inventory_entry_field(Case, symbol_id, RawSymbolId),
816
+ valid_receipt_proof_result(Result) :-
817
+ inventory_entry_field(Result, symbol_id, RawSymbolId),
599
818
  nonempty_receipt_atom(RawSymbolId),
600
- inventory_entry_field(Case, project, RawProject),
601
- nonempty_receipt_atom(RawProject),
602
- inventory_entry_field(Case, outcome, RawOutcome),
603
- normalize_receipt_atom(RawOutcome, Outcome),
604
- memberchk(Outcome, [passed, failed, timed_out, skipped, interrupted]),
605
- inventory_entry_field(Case, retries, Retries),
606
- normalize_integer(Retries, RetryCount),
607
- RetryCount >= 0,
608
- inventory_entry_field(Case, duration_ms, Duration),
609
- normalize_integer(Duration, DurationMs),
610
- DurationMs >= 0.
611
-
612
- receipt_case_key(Case, Key) :-
613
- inventory_entry_field(Case, project, Project),
614
- inventory_entry_field(Case, symbol_id, SymbolId),
615
- normalize_receipt_atom(Project, ProjectAtom),
616
- normalize_receipt_atom(SymbolId, SymbolAtom),
617
- atomic_list_concat([ProjectAtom, SymbolAtom], '/', Key).
819
+ inventory_entry_field(Result, target, RawTarget),
820
+ nonempty_receipt_atom(RawTarget),
821
+ inventory_entry_field(Result, outcome, RawOutcome),
822
+ normalize_receipt_atom(RawOutcome, ResultOutcome),
823
+ memberchk(ResultOutcome, [passed, failed, timed_out, skipped, interrupted, errored]),
824
+ inventory_entry_field(Result, binding, RawBinding),
825
+ normalize_receipt_atom(RawBinding, Binding),
826
+ memberchk(Binding, [native_case, aggregate_run]),
827
+ valid_receipt_attempts(Result).
828
+
829
+ valid_receipt_attempts(Result) :-
830
+ inventory_entry_field(Result, attempts, Attempts),
831
+ inventory_entry_field(Attempts, status, RawStatus),
832
+ normalize_receipt_atom(RawStatus, Status),
833
+ valid_attempt_status(Status, Attempts).
834
+
835
+ valid_attempt_status(unavailable, Attempts) :-
836
+ \+ inventory_entry_field(Attempts, entries, _Present), !.
837
+ valid_attempt_status(unavailable, Attempts) :-
838
+ inventory_entry_field(Attempts, entries, Entries),
839
+ Entries == [], !.
840
+ valid_attempt_status(complete, Attempts) :-
841
+ inventory_entry_field(Attempts, entries, Entries),
842
+ is_list(Entries),
843
+ Entries \= [],
844
+ forall(member(Entry, Entries),
845
+ (inventory_entry_field(Entry, outcome, RawEntryOutcome),
846
+ normalize_receipt_atom(RawEntryOutcome, EntryOutcome),
847
+ memberchk(EntryOutcome, [passed, failed, timed_out, skipped, interrupted, errored]))),
848
+ !.
618
849
 
619
850
  valid_sha256(Value) :-
620
851
  atom_length(Value, 64),
@@ -622,7 +853,7 @@ valid_sha256(Value) :-
622
853
  forall(member(Char, Chars), memberchk(Char, ['0','1','2','3','4','5','6','7','8','9',a,b,c,d,e,f])).
623
854
 
624
855
  valid_receipt_id(Value) :-
625
- re_match('^VR-[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$', Value).
856
+ re_match('^PR-[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$', Value).
626
857
 
627
858
  receipt_timestamp(Receipt, Field, Stamp) :-
628
859
  inventory_entry_field(Receipt, Field, RawValue),
@@ -647,6 +878,29 @@ receipt_for_snapshot(Snapshot, Receipt) :-
647
878
  inventory_entry_field(Receipt, code_snapshot, RawSnapshot),
648
879
  normalize_receipt_atom(RawSnapshot, Snapshot).
649
880
 
881
+ % Per-contract binding selector (W2). In per_contract mode a receipt carrying
882
+ % a binding_hash is current when it matches the test's current binding hash;
883
+ % receipts without one keep the strict snapshot semantics so older evidence
884
+ % stays valid until each contract is re-proven.
885
+ receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts) :-
886
+ ( Context.proofBindingMode == per_contract,
887
+ get_dict(TestId, Context.proofTestBindings, CurrentBinding)
888
+ -> include(receipt_with_binding_hash, Receipts, BoundReceipts),
889
+ include(receipt_for_binding(CurrentBinding), BoundReceipts, SnapshotReceipts),
890
+ ( SnapshotReceipts == []
891
+ -> include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
892
+ ; true
893
+ )
894
+ ; include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
895
+ ).
896
+
897
+ receipt_with_binding_hash(Receipt) :-
898
+ inventory_entry_field(Receipt, binding_hash, _).
899
+
900
+ receipt_for_binding(Binding, Receipt) :-
901
+ inventory_entry_field(Receipt, binding_hash, RawBinding),
902
+ normalize_receipt_atom(RawBinding, Binding).
903
+
650
904
  receipt_matches_current_binding(Scope, ContractBinding, Receipt) :-
651
905
  inventory_entry_field(Receipt, scope, RawScope),
652
906
  normalize_receipt_atom(RawScope, Scope),
@@ -655,7 +909,7 @@ receipt_matches_current_binding(Scope, ContractBinding, Receipt) :-
655
909
  receipt_matches_current_contract(none, _Receipt).
656
910
  receipt_matches_current_contract(hash(ExpectedHash), Receipt) :-
657
911
  inventory_entry_field(Receipt, version, RawVersion),
658
- normalize_receipt_atom(RawVersion, 'kibi.verification-receipt.v2'),
912
+ normalize_receipt_atom(RawVersion, 'kibi.proof-receipt.v1'),
659
913
  inventory_entry_field(Receipt, contract_hash, RawContractHash),
660
914
  normalize_receipt_atom(RawContractHash, ExpectedHash).
661
915
 
@@ -680,26 +934,26 @@ receipt_contract_mismatch_evidence(TestId, Scope, Receipts, ContractBinding, Evi
680
934
  current_contract_hash_value(hash(Hash), Hash).
681
935
  current_contract_hash_value(none, none).
682
936
 
683
- verification_contract_binding(Props, Binding) :-
684
- ( memberchk(verification_contract=RawContract, Props)
685
- -> (verification_contract_hash(RawContract, Hash) -> Binding = hash(Hash) ; Binding = invalid)
937
+ proof_contract_binding(Props, Binding) :-
938
+ ( memberchk(proof_contract=RawContract, Props)
939
+ -> (proof_contract_hash(RawContract, Hash) -> Binding = hash(Hash) ; Binding = invalid)
686
940
  ; Binding = none
687
941
  ).
688
942
 
689
- verification_contract_hash(RawContract, Hash) :-
690
- verification_contract_dict(RawContract, Contract),
943
+ proof_contract_hash(RawContract, Hash) :-
944
+ proof_contract_dict(RawContract, Contract),
691
945
  canonical_json_value(Contract, Canonical),
692
946
  crypto_data_hash(Canonical, Hash, [algorithm(sha256), encoding(utf8)]).
693
947
 
694
- verification_contract_dict(^^(Value, _), Contract) :- !,
695
- verification_contract_dict(Value, Contract).
696
- verification_contract_dict(literal(type(_, Value)), Contract) :- !,
697
- verification_contract_dict(Value, Contract).
698
- verification_contract_dict(literal(Value), Contract) :- !,
699
- verification_contract_dict(Value, Contract).
700
- verification_contract_dict(Contract, Contract) :-
948
+ proof_contract_dict(^^(Value, _), Contract) :- !,
949
+ proof_contract_dict(Value, Contract).
950
+ proof_contract_dict(literal(type(_, Value)), Contract) :- !,
951
+ proof_contract_dict(Value, Contract).
952
+ proof_contract_dict(literal(Value), Contract) :- !,
953
+ proof_contract_dict(Value, Contract).
954
+ proof_contract_dict(Contract, Contract) :-
701
955
  is_dict(Contract), !.
702
- verification_contract_dict(Raw, Contract) :-
956
+ proof_contract_dict(Raw, Contract) :-
703
957
  (atom(Raw) ; string(Raw)),
704
958
  catch(atom_json_dict(Raw, Contract, [value_string_as(string)]), _, fail),
705
959
  is_dict(Contract).
@@ -731,23 +985,21 @@ latest_receipt(Receipts, Latest, FinishedStamp) :-
731
985
  last(Sorted, FinishedStamp-Latest).
732
986
 
733
987
  receipt_runtime_state(_Receipt, FinishedStamp, Context, invalid, AgeSeconds) :-
734
- AgeSeconds is Context.verificationCheckedAtStamp - FinishedStamp,
988
+ AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
735
989
  AgeSeconds < -300,
736
990
  !.
737
991
  receipt_runtime_state(_Receipt, FinishedStamp, Context, stale, AgeSeconds) :-
738
- AgeSeconds is Context.verificationCheckedAtStamp - FinishedStamp,
739
- AgeSeconds > Context.verificationMaxAgeSeconds,
992
+ AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
993
+ AgeSeconds > Context.proofMaxAgeSeconds,
740
994
  !.
741
995
  receipt_runtime_state(Receipt, FinishedStamp, Context, State, AgeSeconds) :-
742
- AgeSeconds is Context.verificationCheckedAtStamp - FinishedStamp,
996
+ AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
743
997
  inventory_entry_field(Receipt, outcome, RawOutcome),
744
998
  normalize_receipt_atom(RawOutcome, Outcome),
745
999
  (Outcome == passed -> State = passed ; State = failed).
746
1000
 
747
1001
  receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
748
1002
  receipt_string_field(Receipt, receipt_id, ReceiptId),
749
- receipt_string_field(Receipt, runner, Runner),
750
- receipt_string_field(Receipt, command, Command),
751
1003
  receipt_string_field(Receipt, outcome, Outcome),
752
1004
  receipt_string_field(Receipt, code_snapshot, CodeSnapshot),
753
1005
  receipt_string_field(Receipt, environment_hash, EnvironmentHash),
@@ -759,8 +1011,6 @@ receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
759
1011
  state: State,
760
1012
  scope: Scope,
761
1013
  receiptId: ReceiptId,
762
- runner: Runner,
763
- command: Command,
764
1014
  outcome: Outcome,
765
1015
  codeSnapshot: CodeSnapshot,
766
1016
  environmentHash: EnvironmentHash,
@@ -828,15 +1078,19 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
828
1078
  include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
829
1079
  exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
830
1080
  include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredSymbols),
831
- production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status),
1081
+ production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status, StatusReason),
832
1082
  maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
833
- Stage = _{
1083
+ StageBase = _{
834
1084
  status: Status,
835
1085
  symbols: ProductionSymbols,
836
1086
  structuralSymbols: StructuralSymbols,
837
1087
  uncoveredSymbols: UncoveredSymbols,
838
1088
  coordinates: Coordinates
839
- }.
1089
+ },
1090
+ ( Status == passed
1091
+ -> Stage = StageBase
1092
+ ; put_dict(reason, StageBase, StatusReason, Stage)
1093
+ ).
840
1094
 
841
1095
  type_shape_symbol_with_structural_contract(SymbolId) :-
842
1096
  kb_entity(SymbolId, symbol, SymbolProps),
@@ -849,10 +1103,26 @@ type_shape_symbol_with_structural_contract(SymbolId) :-
849
1103
  normalize_atom(RawStatus, Status),
850
1104
  memberchk(Status, [active, passing]).
851
1105
 
852
- production_stage_status([], [], _, _, missing) :- !.
853
- production_stage_status(_, _, [], _, blocked) :- !.
854
- production_stage_status(_, _, _, [], passed) :- !.
855
- production_stage_status(_, _, _, _, missing).
1106
+ % Stage status plus a typed reason, from a single total clause so status and
1107
+ % reason can never disagree (the Align stale-receipts case reported `blocked`
1108
+ % with no indication that fresh receipts were the unblock). Clause order keeps
1109
+ % the historical precedence: no production symbols at all is `missing` even
1110
+ % when E2E evidence is also absent; only then does absent E2E evidence read
1111
+ % as `blocked`.
1112
+ production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status, Reason) :-
1113
+ ( ProductionSymbols == [],
1114
+ StructuralSymbols == []
1115
+ -> Status = missing,
1116
+ Reason = "no production symbols implement this requirement; link at least one with implements"
1117
+ ; PassingE2eTests == []
1118
+ -> Status = blocked,
1119
+ Reason = "no passing E2E test evidence is available for the current snapshot; run kibi prove so this stage can evaluate production symbol coverage"
1120
+ ; UncoveredSymbols == []
1121
+ -> Status = passed
1122
+ ; Status = missing,
1123
+ length(UncoveredSymbols, Count),
1124
+ format(atom(Reason), "~w production symbol(s) lack covered_by links to the passing E2E tests", [Count])
1125
+ ).
856
1126
 
857
1127
  symbol_not_covered_by_tests(Tests, SymbolId) :-
858
1128
  \+ (member(TestId, Tests), kb_relationship(covered_by, SymbolId, TestId)).
@@ -988,8 +1258,9 @@ proof_advisories(Stages, Advisories) :-
988
1258
  proof_issues(Stages, _, Advisories).
989
1259
 
990
1260
  % implements REQ-kibi-conservative-requirement-proof
991
- % proofGaps are blocking only. Receipt-completeness codes become
992
- % proofAdvisories when passingE2e already supplies strict proof.
1261
+ % Every linked scenario is a mandatory proof obligation. Receipt-completeness
1262
+ % failures therefore remain blocking even when another scenario has a passing
1263
+ % receipt; unit/integration-only evidence stays nonblocking in the E2E lane.
993
1264
  % Invariant: proofStatus == proven implies proofGaps == [].
994
1265
  proof_issues(Stages, Gaps, Advisories) :-
995
1266
  findall(Gap,
@@ -1003,15 +1274,14 @@ proof_issues(Stages, Gaps, Advisories) :-
1003
1274
  proof_issue_advisory(Gap, Stages)),
1004
1275
  Advisories).
1005
1276
 
1006
- receipt_completeness_issue(missing_verification_receipt).
1007
- receipt_completeness_issue(stale_verification_receipt).
1008
- receipt_completeness_issue(failed_verification_receipt).
1009
- receipt_completeness_issue(invalid_verification_receipt).
1010
- receipt_completeness_issue(verification_contract_mismatch).
1277
+ receipt_completeness_issue(missing_proof_receipt).
1278
+ receipt_completeness_issue(stale_proof_receipt).
1279
+ receipt_completeness_issue(failed_proof_receipt).
1280
+ receipt_completeness_issue(invalid_proof_receipt).
1281
+ receipt_completeness_issue(proof_contract_mismatch).
1011
1282
 
1012
- proof_issue_advisory(Gap, Stages) :-
1013
- receipt_completeness_issue(Gap),
1014
- Stages.passingE2e.status == passed.
1283
+ proof_issue_advisory(_Gap, _Stages) :-
1284
+ fail.
1015
1285
 
1016
1286
  proof_gap_present(missing_semantic_inventory, Stages) :- Stages.semanticInventory.propositionCount =:= 0.
1017
1287
  proof_gap_present(incomplete_semantic_inventory, Stages) :- Stages.semanticInventory.missingCount > 0.
@@ -1028,12 +1298,12 @@ proof_gap_present(contradiction_check_incomplete, Stages) :- Stages.contradictio
1028
1298
  proof_gap_present(missing_scenario, Stages) :- Stages.scenarios.status == missing.
1029
1299
  proof_gap_present(missing_scenario_test, Stages) :- Stages.scenarios.status == passed, Stages.scenarioTests.status == missing.
1030
1300
  proof_gap_present(missing_passing_e2e, Stages) :- Stages.passingE2e.status == missing.
1031
- proof_gap_present(missing_verification_receipt, Stages) :- Stages.passingE2e.missingReceiptTests \= [].
1032
- proof_gap_present(stale_verification_receipt, Stages) :- Stages.passingE2e.staleReceiptTests \= [].
1033
- proof_gap_present(failed_verification_receipt, Stages) :- Stages.passingE2e.failedReceiptTests \= [].
1034
- proof_gap_present(invalid_verification_receipt, Stages) :- Stages.passingE2e.invalidReceiptTests \= [].
1035
- proof_gap_present(verification_contract_mismatch, Stages) :- Stages.passingE2e.contractMismatchReceiptTests \= [].
1036
- proof_gap_present(verification_snapshot_unavailable, Stages) :- Stages.passingE2e.snapshotUnavailableTests \= [].
1301
+ proof_gap_present(missing_proof_receipt, Stages) :- Stages.passingE2e.missingReceiptTests \= [].
1302
+ proof_gap_present(stale_proof_receipt, Stages) :- Stages.passingE2e.staleReceiptTests \= [].
1303
+ proof_gap_present(failed_proof_receipt, Stages) :- Stages.passingE2e.failedReceiptTests \= [].
1304
+ proof_gap_present(invalid_proof_receipt, Stages) :- Stages.passingE2e.invalidReceiptTests \= [].
1305
+ proof_gap_present(proof_contract_mismatch, Stages) :- Stages.passingE2e.contractMismatchReceiptTests \= [].
1306
+ proof_gap_present(proof_snapshot_unavailable, Stages) :- Stages.passingE2e.snapshotUnavailableTests \= [].
1037
1307
  proof_gap_present(missing_executable_test_symbol, Stages) :- Stages.executableSymbols.status == missing.
1038
1308
  proof_gap_present(missing_production_symbol, Stages) :-
1039
1309
  Stages.productionSymbols.symbols == [],
@@ -1055,12 +1325,12 @@ gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete log
1055
1325
  gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
1056
1326
  gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
1057
1327
  gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
1058
- gap_definition(missing_verification_receipt, 53, passing_e2e, "Run the exact current verification contract through kibi verify and append its kibi.verification-receipt.v2 result for the scenario-backed E2E test.").
1059
- gap_definition(stale_verification_receipt, 54, passing_e2e, "Re-run the E2E test against the current code snapshot and append its receipt.").
1060
- gap_definition(failed_verification_receipt, 55, passing_e2e, "Repair the failing E2E behavior and append a newer passing receipt for the same snapshot.").
1061
- gap_definition(invalid_verification_receipt, 56, passing_e2e, "Repair malformed, future-dated, mismatched, or otherwise uncheckable receipt evidence.").
1062
- gap_definition(verification_contract_mismatch, 57, passing_e2e, "Run the current verification contract and append its receipt without rewriting historical evidence.").
1063
- gap_definition(verification_snapshot_unavailable, 58, passing_e2e, "Run coverage through a CLI or MCP runtime that exposes the deterministic workspace snapshot.").
1328
+ gap_definition(missing_proof_receipt, 53, passing_e2e, "Run the configured proof integration through kibi prove and append its kibi.proof-receipt.v1 result for the scenario-backed proof-bearing test.").
1329
+ gap_definition(stale_proof_receipt, 54, passing_e2e, "Re-run kibi prove against the current code snapshot and append fresh receipts.").
1330
+ gap_definition(failed_proof_receipt, 55, passing_e2e, "Repair the failing proof obligation and append a newer passing receipt for the same snapshot.").
1331
+ gap_definition(invalid_proof_receipt, 56, passing_e2e, "Repair malformed, future-dated, mismatched, or otherwise uncheckable receipt evidence.").
1332
+ gap_definition(proof_contract_mismatch, 57, passing_e2e, "Run kibi prove so the receipt carries the current proof contract hash and execution fingerprint without rewriting historical evidence.").
1333
+ gap_definition(proof_snapshot_unavailable, 58, passing_e2e, "Run coverage through a CLI or MCP runtime that exposes the deterministic workspace snapshot.").
1064
1334
  gap_definition(missing_executable_test_symbol, 60, executable_symbols, "Link executable test code to every qualifying E2E test with executable_for.").
1065
1335
  gap_definition(missing_production_symbol, 70, production_symbols, "Link at least one production symbol to the requirement with implements.").
1066
1336
  gap_definition(missing_production_symbol_coverage, 71, production_symbols, "Link every runtime implementing production symbol to a qualifying E2E test with covered_by; type-shape symbols retain structural unit contracts instead.").