kibi-core 0.9.1 → 0.11.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.
package/src/logic_ir.pl CHANGED
@@ -530,7 +530,7 @@ rule_dependency_edge(
530
530
  logic_predicate_signature(atom(Namespace, Name, Args, _Polarity, _ClosedWorld), predicate(Namespace, Name, Arity)) :-
531
531
  length(Args, Arity).
532
532
 
533
- expression_dependency(atom(_Namespace, _Name, _Args, _Polarity, _ClosedWorld), Atom, positive) :-
533
+ expression_dependency(Atom, Atom, positive) :-
534
534
  Atom = atom(_, _, _, _, _).
535
535
  expression_dependency(not(Atom), Atom, negative).
536
536
  expression_dependency(all(Items), Atom, Sign) :- member(Item, Items), expression_dependency(Item, Atom, Sign).
@@ -8,12 +8,12 @@
8
8
  ]).
9
9
 
10
10
  :- use_module(library(http/json)).
11
+ :- use_module(library(crypto)).
11
12
  :- use_module(library(date)).
12
13
  :- use_module(library(pcre)).
13
14
  :- use_module('kb.pl').
14
15
  :- use_module('checks.pl', [
15
- check_domain_contradictions/1,
16
- check_domain_contradiction_witnesses/1,
16
+ check_domain_contradictions_and_witnesses/2,
17
17
  check_rule_safety/1,
18
18
  check_rule_verifiability/1
19
19
  ]).
@@ -25,8 +25,7 @@ requirement_proof_context(Context) :-
25
25
  requirement_proof_context(unknown, '1970-01-01T00:00:00Z', 604800, Context).
26
26
 
27
27
  requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, Context) :-
28
- check_domain_contradictions(Contradictions),
29
- check_domain_contradiction_witnesses(ContradictionWitnesses),
28
+ check_domain_contradictions_and_witnesses(Contradictions, ContradictionWitnesses),
30
29
  check_rule_safety(UnsafeRules),
31
30
  check_rule_verifiability(UnverifiableRules),
32
31
  normalize_atom(VerificationSnapshot, Snapshot),
@@ -52,6 +51,7 @@ requirement_proof(ReqId, _ReqProps, _Context, Proof) :-
52
51
  proofVersion: Version,
53
52
  proofStatus: not_applicable,
54
53
  proofGaps: [],
54
+ proofAdvisories: [],
55
55
  proofRepairs: [],
56
56
  proofStages: _{applicability: _{status: not_applicable}}
57
57
  }.
@@ -76,7 +76,7 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
76
76
  productionSymbols: ProductionStage,
77
77
  sourceCoordinates: CoordinateStage
78
78
  },
79
- proof_gaps(Stages, Gaps),
79
+ proof_issues(Stages, Gaps, Advisories),
80
80
  proof_repairs(Gaps, Repairs),
81
81
  proof_status(Stages, Status),
82
82
  proof_version(Version),
@@ -84,6 +84,7 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
84
84
  proofVersion: Version,
85
85
  proofStatus: Status,
86
86
  proofGaps: Gaps,
87
+ proofAdvisories: Advisories,
87
88
  proofRepairs: Repairs,
88
89
  proofStages: Stages
89
90
  }.
@@ -224,6 +225,7 @@ logic_grounding_stage(ReqId, Props, Inventory, Context, Stage) :-
224
225
  InvalidRuleFactIds,
225
226
  Status
226
227
  ),
228
+ ground_source_refs(Evidence, GroundSources),
227
229
  Stage = _{
228
230
  status: Status,
229
231
  manifestClaims: ManifestKeys,
@@ -238,7 +240,8 @@ logic_grounding_stage(ReqId, Props, Inventory, Context, Stage) :-
238
240
  missingManifestClaims: MissingManifestKeys,
239
241
  extraManifestClaims: ExtraManifestKeys,
240
242
  undeclaredGroundClaims: UndeclaredGroundKeys,
241
- invalidRuleFacts: InvalidRuleFactIds
243
+ invalidRuleFacts: InvalidRuleFactIds,
244
+ sources: GroundSources
242
245
  }.
243
246
 
244
247
  requirement_claim_keys(Props, true, Keys) :-
@@ -436,7 +439,8 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
436
439
  ScenarioIds0),
437
440
  sort(ScenarioIds0, ScenarioIds),
438
441
  (ScenarioIds = [] -> Status = missing ; Status = passed),
439
- Stage = _{status: Status, scenarios: ScenarioIds}.
442
+ maplist(entity_source_ref, ScenarioIds, Sources),
443
+ Stage = _{status: Status, scenarios: ScenarioIds, sources: Sources}.
440
444
 
441
445
  scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
442
446
  findall(TestId,
@@ -444,7 +448,8 @@ scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
444
448
  ScenarioTests0),
445
449
  sort(ScenarioTests0, ScenarioTests),
446
450
  (ScenarioTests = [] -> Status = missing ; Status = passed),
447
- Stage = _{status: Status, tests: ScenarioTests}.
451
+ maplist(entity_source_ref, ScenarioTests, Sources),
452
+ Stage = _{status: Status, tests: ScenarioTests, sources: Sources}.
448
453
 
449
454
  scenario_test(ScenarioId, TestId) :-
450
455
  kb_relationship(verified_by, ScenarioId, TestId),
@@ -460,9 +465,10 @@ passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
460
465
  evidence_tests_with_state(Evidence, stale, StaleReceiptTests),
461
466
  evidence_tests_with_state(Evidence, failed, FailedReceiptTests),
462
467
  evidence_tests_with_state(Evidence, invalid, InvalidReceiptTests),
468
+ evidence_tests_with_state(Evidence, contract_mismatch, ContractMismatchReceiptTests),
463
469
  evidence_tests_with_state(Evidence, snapshot_unavailable, SnapshotUnavailableTests),
464
470
  evidence_tests_with_state(Evidence, not_end_to_end, NonEndToEndTests),
465
- passing_e2e_status(PassingE2eTests, InvalidReceiptTests, SnapshotUnavailableTests, Status),
471
+ passing_e2e_status(PassingE2eTests, InvalidReceiptTests, ContractMismatchReceiptTests, SnapshotUnavailableTests, Status),
466
472
  Stage = _{
467
473
  status: Status,
468
474
  tests: PassingE2eTests,
@@ -471,6 +477,7 @@ passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
471
477
  staleReceiptTests: StaleReceiptTests,
472
478
  failedReceiptTests: FailedReceiptTests,
473
479
  invalidReceiptTests: InvalidReceiptTests,
480
+ contractMismatchReceiptTests: ContractMismatchReceiptTests,
474
481
  snapshotUnavailableTests: SnapshotUnavailableTests,
475
482
  nonEndToEndTests: NonEndToEndTests,
476
483
  currentCodeSnapshot: Context.verificationSnapshot,
@@ -478,10 +485,11 @@ passing_e2e_stage(ScenarioTests, Context, Stage, PassingE2eTests) :-
478
485
  maxAgeSeconds: Context.verificationMaxAgeSeconds
479
486
  }.
480
487
 
481
- passing_e2e_status(Passing, _, _, passed) :- Passing \= [], !.
482
- passing_e2e_status([], Invalid, _, unresolved) :- Invalid \= [], !.
483
- passing_e2e_status([], _, SnapshotUnavailable, unresolved) :- SnapshotUnavailable \= [], !.
484
- passing_e2e_status([], _, _, missing).
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).
485
493
 
486
494
  evidence_tests_with_state(Evidence, State, TestIds) :-
487
495
  findall(TestId,
@@ -497,26 +505,33 @@ test_receipt_evidence(Context, TestId, Evidence) :-
497
505
  ; Context.verificationSnapshot == unknown
498
506
  -> Evidence = _{testId: TestId, state: snapshot_unavailable, scope: Scope}
499
507
  ; verification_receipt_entries(Props, Receipts, ReceiptPropertyPresent),
500
- receipt_evidence_state(TestId, Scope, Receipts, ReceiptPropertyPresent, Context, Evidence)
508
+ receipt_evidence_state(TestId, Scope, Props, Receipts, ReceiptPropertyPresent, Context, Evidence)
501
509
  ).
502
510
 
503
- receipt_evidence_state(TestId, Scope, [], false, _Context,
511
+ receipt_evidence_state(TestId, Scope, _Props, [], false, _Context,
504
512
  _{testId: TestId, state: missing, scope: Scope, receiptCount: 0}) :- !.
505
- receipt_evidence_state(TestId, Scope, Receipts, _Present, _Context,
513
+ receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, _Context,
506
514
  _{testId: TestId, state: invalid, scope: Scope, receiptCount: Count}) :-
507
515
  length(Receipts, Count),
508
- (Receipts == []
509
- ; member(Receipt, Receipts), \+ valid_receipt_shape(TestId, Scope, Receipt)
516
+ verification_contract_binding(Props, ContractBinding),
517
+ (ContractBinding == invalid
518
+ ; Receipts == []
519
+ ; member(Receipt, Receipts), \+ valid_receipt_shape(TestId, Receipt)
510
520
  ; \+ chronological_receipt_history(Receipts)),
511
521
  !.
512
- receipt_evidence_state(TestId, Scope, Receipts, _Present, Context, Evidence) :-
513
- include(receipt_for_snapshot(Context.verificationSnapshot), Receipts, CurrentReceipts),
514
- ( CurrentReceipts == []
522
+ receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Evidence) :-
523
+ include(receipt_for_snapshot(Context.verificationSnapshot), Receipts, SnapshotReceipts),
524
+ ( SnapshotReceipts == []
515
525
  -> length(Receipts, Count),
516
526
  Evidence = _{testId: TestId, state: stale, scope: Scope, receiptCount: Count}
517
- ; latest_receipt(CurrentReceipts, Latest, FinishedStamp),
518
- receipt_runtime_state(Latest, FinishedStamp, Context, State, AgeSeconds),
519
- receipt_evidence_dict(TestId, Scope, Latest, State, AgeSeconds, Evidence)
527
+ ; verification_contract_binding(Props, ContractBinding),
528
+ include(receipt_matches_current_binding(Scope, ContractBinding), SnapshotReceipts, CurrentReceipts),
529
+ ( CurrentReceipts == []
530
+ -> receipt_contract_mismatch_evidence(TestId, Scope, SnapshotReceipts, ContractBinding, Evidence)
531
+ ; latest_receipt(CurrentReceipts, Latest, FinishedStamp),
532
+ receipt_runtime_state(Latest, FinishedStamp, Context, State, AgeSeconds),
533
+ receipt_evidence_dict(TestId, Scope, Latest, State, AgeSeconds, Evidence)
534
+ )
520
535
  ).
521
536
 
522
537
  verification_receipt_entries(Props, Receipts, Present) :-
@@ -527,9 +542,10 @@ verification_receipt_entries(Props, Receipts, Present) :-
527
542
  Receipts = []
528
543
  ).
529
544
 
530
- valid_receipt_shape(TestId, Scope, Receipt) :-
545
+ valid_receipt_shape(TestId, Receipt) :-
531
546
  inventory_entry_field(Receipt, version, RawVersion),
532
- normalize_receipt_atom(RawVersion, 'kibi.verification-receipt.v1'),
547
+ normalize_receipt_atom(RawVersion, Version),
548
+ memberchk(Version, ['kibi.verification-receipt.v1', 'kibi.verification-receipt.v2']),
533
549
  inventory_entry_field(Receipt, receipt_id, RawReceiptId),
534
550
  normalize_receipt_atom(RawReceiptId, ReceiptId), valid_receipt_id(ReceiptId),
535
551
  inventory_entry_field(Receipt, test_id, RawTestId),
@@ -539,10 +555,11 @@ valid_receipt_shape(TestId, Scope, Receipt) :-
539
555
  inventory_entry_field(Receipt, command, RawCommand),
540
556
  normalize_receipt_atom(RawCommand, Command), Command \= '',
541
557
  inventory_entry_field(Receipt, scope, RawScope),
542
- normalize_receipt_atom(RawScope, Scope),
558
+ normalize_receipt_atom(RawScope, ReceiptScope),
559
+ memberchk(ReceiptScope, [unit, integration, end_to_end]),
543
560
  inventory_entry_field(Receipt, outcome, RawOutcome),
544
561
  normalize_receipt_atom(RawOutcome, Outcome),
545
- memberchk(Outcome, [passed, failed, errored, cancelled, skipped]),
562
+ memberchk(Outcome, [passed, failed, errored, cancelled, skipped, timed_out, interrupted]),
546
563
  inventory_entry_field(Receipt, code_snapshot, RawSnapshot),
547
564
  normalize_receipt_atom(RawSnapshot, Snapshot), valid_sha256(Snapshot),
548
565
  inventory_entry_field(Receipt, environment_hash, RawEnvironmentHash),
@@ -551,7 +568,53 @@ valid_receipt_shape(TestId, Scope, Receipt) :-
551
568
  normalize_receipt_atom(RawArtifactDigest, ArtifactDigest), valid_sha256(ArtifactDigest),
552
569
  receipt_timestamp(Receipt, started_at, StartedStamp),
553
570
  receipt_timestamp(Receipt, finished_at, FinishedStamp),
554
- FinishedStamp >= StartedStamp.
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) :-
576
+ inventory_entry_field(Receipt, command_argv, RawCommandArgv),
577
+ is_list(RawCommandArgv),
578
+ RawCommandArgv \= [],
579
+ 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).
592
+
593
+ nonempty_receipt_atom(Value) :-
594
+ normalize_receipt_atom(Value, Atom),
595
+ Atom \= ''.
596
+
597
+ valid_receipt_case(Case) :-
598
+ inventory_entry_field(Case, symbol_id, RawSymbolId),
599
+ 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).
555
618
 
556
619
  valid_sha256(Value) :-
557
620
  atom_length(Value, 64),
@@ -584,6 +647,82 @@ receipt_for_snapshot(Snapshot, Receipt) :-
584
647
  inventory_entry_field(Receipt, code_snapshot, RawSnapshot),
585
648
  normalize_receipt_atom(RawSnapshot, Snapshot).
586
649
 
650
+ receipt_matches_current_binding(Scope, ContractBinding, Receipt) :-
651
+ inventory_entry_field(Receipt, scope, RawScope),
652
+ normalize_receipt_atom(RawScope, Scope),
653
+ receipt_matches_current_contract(ContractBinding, Receipt).
654
+
655
+ receipt_matches_current_contract(none, _Receipt).
656
+ receipt_matches_current_contract(hash(ExpectedHash), Receipt) :-
657
+ inventory_entry_field(Receipt, version, RawVersion),
658
+ normalize_receipt_atom(RawVersion, 'kibi.verification-receipt.v2'),
659
+ inventory_entry_field(Receipt, contract_hash, RawContractHash),
660
+ normalize_receipt_atom(RawContractHash, ExpectedHash).
661
+
662
+ receipt_contract_mismatch_evidence(TestId, Scope, Receipts, ContractBinding, Evidence) :-
663
+ length(Receipts, Count),
664
+ findall(Hash,
665
+ (member(Receipt, Receipts),
666
+ inventory_entry_field(Receipt, contract_hash, RawHash),
667
+ normalize_receipt_atom(RawHash, Hash)),
668
+ Hashes0),
669
+ sort(Hashes0, Hashes),
670
+ current_contract_hash_value(ContractBinding, CurrentContractHash),
671
+ Evidence = _{
672
+ testId: TestId,
673
+ state: contract_mismatch,
674
+ scope: Scope,
675
+ receiptCount: Count,
676
+ currentContractHash: CurrentContractHash,
677
+ receiptContractHashes: Hashes
678
+ }.
679
+
680
+ current_contract_hash_value(hash(Hash), Hash).
681
+ current_contract_hash_value(none, none).
682
+
683
+ verification_contract_binding(Props, Binding) :-
684
+ ( memberchk(verification_contract=RawContract, Props)
685
+ -> (verification_contract_hash(RawContract, Hash) -> Binding = hash(Hash) ; Binding = invalid)
686
+ ; Binding = none
687
+ ).
688
+
689
+ verification_contract_hash(RawContract, Hash) :-
690
+ verification_contract_dict(RawContract, Contract),
691
+ canonical_json_value(Contract, Canonical),
692
+ crypto_data_hash(Canonical, Hash, [algorithm(sha256), encoding(utf8)]).
693
+
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) :-
701
+ is_dict(Contract), !.
702
+ verification_contract_dict(Raw, Contract) :-
703
+ (atom(Raw) ; string(Raw)),
704
+ catch(atom_json_dict(Raw, Contract, [value_string_as(string)]), _, fail),
705
+ is_dict(Contract).
706
+
707
+ canonical_json_value(Value, Json) :-
708
+ is_dict(Value), !,
709
+ dict_pairs(Value, _, Pairs),
710
+ maplist(canonical_json_pair, Pairs, Parts),
711
+ atomics_to_string(Parts, ",", Body),
712
+ format(string(Json), "{~s}", [Body]).
713
+ canonical_json_value(Value, Json) :-
714
+ is_list(Value), !,
715
+ maplist(canonical_json_value, Value, Parts),
716
+ atomics_to_string(Parts, ",", Body),
717
+ format(string(Json), "[~s]", [Body]).
718
+ canonical_json_value(Value, Json) :-
719
+ with_output_to(string(Json), json_write(current_output, Value, [width(0)])).
720
+
721
+ canonical_json_pair(Key-Value, Json) :-
722
+ with_output_to(string(KeyJson), json_write(current_output, Key, [width(0)])),
723
+ canonical_json_value(Value, ValueJson),
724
+ format(string(Json), "~s:~s", [KeyJson, ValueJson]).
725
+
587
726
  latest_receipt(Receipts, Latest, FinishedStamp) :-
588
727
  findall(Stamp-Receipt,
589
728
  (member(Receipt, Receipts), receipt_timestamp(Receipt, finished_at, Stamp)),
@@ -662,7 +801,7 @@ test_scope(Props, end_to_end) :-
662
801
  !.
663
802
  test_scope(_, unknown).
664
803
 
665
- executable_symbol_stage([], _{status: blocked, symbols: [], missingTests: []}, []).
804
+ executable_symbol_stage([], _{status: blocked, symbols: [], missingTests: [], coordinates: []}, []).
666
805
  executable_symbol_stage(PassingE2eTests, Stage, Symbols) :-
667
806
  PassingE2eTests \= [],
668
807
  findall(SymbolId,
@@ -673,7 +812,8 @@ executable_symbol_stage(PassingE2eTests, Stage, Symbols) :-
673
812
  sort(Symbols0, Symbols),
674
813
  include(test_missing_executable_symbol, PassingE2eTests, MissingTests),
675
814
  (MissingTests = [] -> Status = passed ; Status = missing),
676
- Stage = _{status: Status, symbols: Symbols, missingTests: MissingTests}.
815
+ maplist(symbol_coordinate_ref, Symbols, Coordinates),
816
+ Stage = _{status: Status, symbols: Symbols, missingTests: MissingTests, coordinates: Coordinates}.
677
817
 
678
818
  test_missing_executable_symbol(TestId) :-
679
819
  \+ (kb_relationship(executable_for, SymbolId, TestId), kb_entity(SymbolId, symbol, _)).
@@ -683,16 +823,36 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
683
823
  (kb_relationship(implements, SymbolId, ReqId),
684
824
  kb_entity(SymbolId, symbol, _),
685
825
  \+ kb:executable_test_symbol(SymbolId)),
686
- Production0),
687
- sort(Production0, ProductionSymbols),
826
+ Implementing0),
827
+ sort(Implementing0, ImplementingSymbols),
828
+ include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
829
+ exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
688
830
  include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredSymbols),
689
- production_stage_status(ProductionSymbols, PassingE2eTests, UncoveredSymbols, Status),
690
- Stage = _{status: Status, symbols: ProductionSymbols, uncoveredSymbols: UncoveredSymbols}.
831
+ production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status),
832
+ maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
833
+ Stage = _{
834
+ status: Status,
835
+ symbols: ProductionSymbols,
836
+ structuralSymbols: StructuralSymbols,
837
+ uncoveredSymbols: UncoveredSymbols,
838
+ coordinates: Coordinates
839
+ }.
691
840
 
692
- production_stage_status([], _, _, missing) :- !.
693
- production_stage_status(_, [], _, blocked) :- !.
694
- production_stage_status(_, _, [], passed) :- !.
695
- production_stage_status(_, _, _, missing).
841
+ type_shape_symbol_with_structural_contract(SymbolId) :-
842
+ kb_entity(SymbolId, symbol, SymbolProps),
843
+ memberchk(symbol_role=RawRole, SymbolProps),
844
+ normalize_atom(RawRole, 'type-shape'),
845
+ kb_relationship(covered_by, SymbolId, TestId),
846
+ kb_entity(TestId, test, TestProps),
847
+ test_scope(TestProps, unit),
848
+ memberchk(status=RawStatus, TestProps),
849
+ normalize_atom(RawStatus, Status),
850
+ memberchk(Status, [active, passing]).
851
+
852
+ production_stage_status([], [], _, _, missing) :- !.
853
+ production_stage_status(_, _, [], _, blocked) :- !.
854
+ production_stage_status(_, _, _, [], passed) :- !.
855
+ production_stage_status(_, _, _, _, missing).
696
856
 
697
857
  symbol_not_covered_by_tests(Tests, SymbolId) :-
698
858
  \+ (member(TestId, Tests), kb_relationship(covered_by, SymbolId, TestId)).
@@ -707,11 +867,15 @@ source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, Stage) :
707
867
  ; RequirementSource = present,
708
868
  coordinate_stage_status(Symbols, MissingSymbols, Status)
709
869
  ),
870
+ requirement_source_path(ReqProps, RequirementPath),
871
+ maplist(symbol_coordinate_ref, Symbols, Coordinates),
710
872
  Stage = _{
711
873
  status: Status,
712
874
  requirementSource: RequirementSource,
875
+ requirementPath: RequirementPath,
713
876
  symbols: Symbols,
714
- missingSymbols: MissingSymbols
877
+ missingSymbols: MissingSymbols,
878
+ coordinates: Coordinates
715
879
  }.
716
880
 
717
881
  coordinate_stage_status([], _, blocked) :- !.
@@ -724,7 +888,7 @@ symbol_missing_coordinates(SymbolId) :-
724
888
 
725
889
  valid_symbol_coordinates(Props) :-
726
890
  memberchk(sourceFile=RawSourceFile, Props),
727
- normalize_atom(RawSourceFile, SourceFile),
891
+ source_path_atom(RawSourceFile, SourceFile),
728
892
  SourceFile \= '',
729
893
  memberchk(sourceLine=RawLine, Props),
730
894
  memberchk(sourceColumn=RawColumn, Props),
@@ -739,10 +903,66 @@ valid_symbol_coordinates(Props) :-
739
903
  EndLine >= Line,
740
904
  EndColumn >= 0.
741
905
 
742
- nonempty_source(Props) :-
906
+ % Prefer sourceFile, then source, without URI-basename stripping. Paths must
907
+ % remain inspectable coordinates for report linking.
908
+ proof_source_property(Props, RawSource) :-
909
+ memberchk(sourceFile=RawSource, Props),
910
+ source_path_atom(RawSource, _),
911
+ !.
912
+ proof_source_property(Props, RawSource) :-
743
913
  memberchk(source=RawSource, Props),
744
- normalize_atom(RawSource, Source),
745
- Source \= ''.
914
+ source_path_atom(RawSource, _).
915
+
916
+ nonempty_source(Props) :-
917
+ proof_source_property(Props, _).
918
+
919
+ requirement_source_path(Props, Path) :-
920
+ proof_source_property(Props, RawSource),
921
+ source_path_atom(RawSource, Path),
922
+ !.
923
+ requirement_source_path(_, '').
924
+
925
+ entity_source_path(Id, Path) :-
926
+ kb_entity(Id, _, Props),
927
+ requirement_source_path(Props, Path).
928
+
929
+ entity_source_ref(Id, _{id: Id, path: Path}) :-
930
+ entity_source_path(Id, Path).
931
+
932
+ ground_source_refs(Evidence, Refs) :-
933
+ findall(Ref,
934
+ (member(Item, Evidence),
935
+ entity_source_ref(Item.factId, Ref),
936
+ Ref.path \= ''),
937
+ Refs0),
938
+ sort(Refs0, Refs).
939
+
940
+ symbol_coordinate_ref(SymbolId, Ref) :-
941
+ kb_entity(SymbolId, symbol, Props),
942
+ memberchk(sourceFile=RawFile, Props),
943
+ source_path_atom(RawFile, File),
944
+ File \= '',
945
+ !,
946
+ ( valid_symbol_coordinates(Props)
947
+ -> memberchk(sourceLine=RawLine, Props),
948
+ memberchk(sourceColumn=RawColumn, Props),
949
+ memberchk(sourceEndLine=RawEndLine, Props),
950
+ memberchk(sourceEndColumn=RawEndColumn, Props),
951
+ normalize_integer(RawLine, Line),
952
+ normalize_integer(RawColumn, Column),
953
+ normalize_integer(RawEndLine, EndLine),
954
+ normalize_integer(RawEndColumn, EndColumn),
955
+ Ref = _{
956
+ id: SymbolId,
957
+ path: File,
958
+ line: Line,
959
+ column: Column,
960
+ endLine: EndLine,
961
+ endColumn: EndColumn
962
+ }
963
+ ; Ref = _{id: SymbolId, path: File}
964
+ ).
965
+ symbol_coordinate_ref(SymbolId, _{id: SymbolId, path: ''}).
746
966
 
747
967
  proof_status(Stages, unresolved) :-
748
968
  Stages.contradictions.status == blocked,
@@ -762,7 +982,36 @@ stage_dict(Stages, Stage) :-
762
982
  member(_-Stage, Pairs).
763
983
 
764
984
  proof_gaps(Stages, Gaps) :-
765
- findall(Gap, (gap_definition(Gap, _, _, _), proof_gap_present(Gap, Stages)), Gaps).
985
+ proof_issues(Stages, Gaps, _).
986
+
987
+ proof_advisories(Stages, Advisories) :-
988
+ proof_issues(Stages, _, Advisories).
989
+
990
+ % implements REQ-kibi-conservative-requirement-proof
991
+ % proofGaps are blocking only. Receipt-completeness codes become
992
+ % proofAdvisories when passingE2e already supplies strict proof.
993
+ % Invariant: proofStatus == proven implies proofGaps == [].
994
+ proof_issues(Stages, Gaps, Advisories) :-
995
+ findall(Gap,
996
+ (gap_definition(Gap, _, _, _),
997
+ proof_gap_present(Gap, Stages),
998
+ \+ proof_issue_advisory(Gap, Stages)),
999
+ Gaps),
1000
+ findall(Gap,
1001
+ (gap_definition(Gap, _, _, _),
1002
+ proof_gap_present(Gap, Stages),
1003
+ proof_issue_advisory(Gap, Stages)),
1004
+ Advisories).
1005
+
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).
1011
+
1012
+ proof_issue_advisory(Gap, Stages) :-
1013
+ receipt_completeness_issue(Gap),
1014
+ Stages.passingE2e.status == passed.
766
1015
 
767
1016
  proof_gap_present(missing_semantic_inventory, Stages) :- Stages.semanticInventory.propositionCount =:= 0.
768
1017
  proof_gap_present(incomplete_semantic_inventory, Stages) :- Stages.semanticInventory.missingCount > 0.
@@ -783,9 +1032,12 @@ proof_gap_present(missing_verification_receipt, Stages) :- Stages.passingE2e.mis
783
1032
  proof_gap_present(stale_verification_receipt, Stages) :- Stages.passingE2e.staleReceiptTests \= [].
784
1033
  proof_gap_present(failed_verification_receipt, Stages) :- Stages.passingE2e.failedReceiptTests \= [].
785
1034
  proof_gap_present(invalid_verification_receipt, Stages) :- Stages.passingE2e.invalidReceiptTests \= [].
1035
+ proof_gap_present(verification_contract_mismatch, Stages) :- Stages.passingE2e.contractMismatchReceiptTests \= [].
786
1036
  proof_gap_present(verification_snapshot_unavailable, Stages) :- Stages.passingE2e.snapshotUnavailableTests \= [].
787
1037
  proof_gap_present(missing_executable_test_symbol, Stages) :- Stages.executableSymbols.status == missing.
788
- proof_gap_present(missing_production_symbol, Stages) :- Stages.productionSymbols.symbols == [].
1038
+ proof_gap_present(missing_production_symbol, Stages) :-
1039
+ Stages.productionSymbols.symbols == [],
1040
+ Stages.productionSymbols.structuralSymbols == [].
789
1041
  proof_gap_present(missing_production_symbol_coverage, Stages) :- Stages.productionSymbols.uncoveredSymbols \= [].
790
1042
  proof_gap_present(missing_symbol_coordinates, Stages) :- Stages.sourceCoordinates.missingSymbols \= [].
791
1043
  proof_gap_present(missing_requirement_source, Stages) :- Stages.sourceCoordinates.requirementSource == missing.
@@ -803,14 +1055,15 @@ gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete log
803
1055
  gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
804
1056
  gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
805
1057
  gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
806
- gap_definition(missing_verification_receipt, 53, passing_e2e, "Record a kibi.verification-receipt.v1 result for the scenario-backed E2E 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.").
807
1059
  gap_definition(stale_verification_receipt, 54, passing_e2e, "Re-run the E2E test against the current code snapshot and append its receipt.").
808
1060
  gap_definition(failed_verification_receipt, 55, passing_e2e, "Repair the failing E2E behavior and append a newer passing receipt for the same snapshot.").
809
1061
  gap_definition(invalid_verification_receipt, 56, passing_e2e, "Repair malformed, future-dated, mismatched, or otherwise uncheckable receipt evidence.").
810
- gap_definition(verification_snapshot_unavailable, 57, passing_e2e, "Run coverage through a CLI or MCP runtime that exposes the deterministic workspace snapshot.").
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.").
811
1064
  gap_definition(missing_executable_test_symbol, 60, executable_symbols, "Link executable test code to every qualifying E2E test with executable_for.").
812
1065
  gap_definition(missing_production_symbol, 70, production_symbols, "Link at least one production symbol to the requirement with implements.").
813
- gap_definition(missing_production_symbol_coverage, 71, production_symbols, "Link every implementing production symbol to a qualifying E2E test with covered_by.").
1066
+ 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.").
814
1067
  gap_definition(missing_symbol_coordinates, 80, source_coordinates, "Refresh and persist exact coordinates for every proof-bearing symbol.").
815
1068
  gap_definition(missing_requirement_source, 81, source_coordinates, "Bind the requirement to its current source document.").
816
1069
 
@@ -822,6 +1075,10 @@ proof_repairs(Gaps, Repairs) :-
822
1075
  normalize_atom(Value, Atom) :-
823
1076
  kb:normalize_term_atom(Value, Atom).
824
1077
 
1078
+ source_path_atom(Value, Atom) :-
1079
+ kb:source_value_atom(Value, Atom),
1080
+ Atom \= ''.
1081
+
825
1082
  normalize_atom_list(Value, Atoms) :-
826
1083
  kb:normalize_term_atom_list(Value, Atoms).
827
1084