kibi-core 0.10.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.
- package/package.json +5 -2
- package/schema/entities.pl +11 -2
- package/schema/relationships.pl +1 -0
- package/schema/validation.pl +3 -1
- package/src/checks.pl +189 -7
- package/src/discovery.pl +108 -5
- package/src/kb.pl +70 -4
- package/src/logic_ir.pl +1 -1
- package/src/requirement_proof.pl +630 -103
- package/src/rule_registry.pl +131 -0
- package/src/status.pl +144 -0
- package/src/symbol_classification.pl +47 -0
package/src/requirement_proof.pl
CHANGED
|
@@ -4,64 +4,81 @@
|
|
|
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
|
|
|
10
11
|
:- use_module(library(http/json)).
|
|
12
|
+
:- use_module(library(crypto)).
|
|
11
13
|
:- use_module(library(date)).
|
|
12
14
|
:- use_module(library(pcre)).
|
|
13
15
|
:- use_module('kb.pl').
|
|
14
16
|
:- use_module('checks.pl', [
|
|
15
|
-
|
|
16
|
-
check_domain_contradiction_witnesses/1,
|
|
17
|
+
check_domain_contradictions_and_witnesses/2,
|
|
17
18
|
check_rule_safety/1,
|
|
18
19
|
check_rule_verifiability/1
|
|
19
20
|
]).
|
|
20
21
|
:- use_module('logic_ir.pl', [logic_rule_safety/2]).
|
|
21
22
|
|
|
22
|
-
proof_version('kibi.requirement-proof.
|
|
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) :-
|
|
28
|
-
|
|
29
|
-
|
|
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) :-
|
|
40
|
+
check_domain_contradictions_and_witnesses(Contradictions, ContradictionWitnesses),
|
|
30
41
|
check_rule_safety(UnsafeRules),
|
|
31
42
|
check_rule_verifiability(UnverifiableRules),
|
|
32
43
|
normalize_atom(VerificationSnapshot, Snapshot),
|
|
33
44
|
normalize_atom(CheckedAt, CheckedAtAtom),
|
|
34
45
|
(catch(parse_time(CheckedAtAtom, CheckedAtStamp), _, fail) -> true ; CheckedAtStamp = -1),
|
|
35
46
|
normalize_integer(MaxAgeSeconds, MaxAge),
|
|
47
|
+
normalize_atom(BindingMode, BindingModeAtom),
|
|
36
48
|
Context = _{
|
|
37
49
|
contradictions: Contradictions,
|
|
38
50
|
contradictionWitnesses: ContradictionWitnesses,
|
|
39
51
|
unsafeRules: UnsafeRules,
|
|
40
52
|
unverifiableRules: UnverifiableRules,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
proofSnapshot: Snapshot,
|
|
54
|
+
proofCheckedAt: CheckedAtAtom,
|
|
55
|
+
proofCheckedAtStamp: CheckedAtStamp,
|
|
56
|
+
proofMaxAgeSeconds: MaxAge,
|
|
57
|
+
proofBindingMode: BindingModeAtom,
|
|
58
|
+
proofTestBindings: TestBindings
|
|
45
59
|
}.
|
|
46
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).
|
|
47
70
|
requirement_proof(ReqId, _ReqProps, _Context, Proof) :-
|
|
48
71
|
\+ kb:current_req(ReqId),
|
|
49
72
|
!,
|
|
50
|
-
|
|
51
|
-
Proof
|
|
52
|
-
proofVersion: Version,
|
|
53
|
-
proofStatus: not_applicable,
|
|
54
|
-
proofGaps: [],
|
|
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,
|
|
64
|
-
passing_e2e_stage(
|
|
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),
|
|
@@ -76,7 +93,7 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
|
76
93
|
productionSymbols: ProductionStage,
|
|
77
94
|
sourceCoordinates: CoordinateStage
|
|
78
95
|
},
|
|
79
|
-
|
|
96
|
+
proof_issues(Stages, Gaps, Advisories),
|
|
80
97
|
proof_repairs(Gaps, Repairs),
|
|
81
98
|
proof_status(Stages, Status),
|
|
82
99
|
proof_version(Version),
|
|
@@ -84,10 +101,50 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
|
84
101
|
proofVersion: Version,
|
|
85
102
|
proofStatus: Status,
|
|
86
103
|
proofGaps: Gaps,
|
|
104
|
+
proofAdvisories: Advisories,
|
|
87
105
|
proofRepairs: Repairs,
|
|
88
106
|
proofStages: Stages
|
|
89
107
|
}.
|
|
90
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
|
+
|
|
91
148
|
semantic_inventory_stage(Props, Stage, Entries) :-
|
|
92
149
|
( memberchk(semantic_inventory=RawInventory, Props),
|
|
93
150
|
inventory_entries(RawInventory, Entries0),
|
|
@@ -224,6 +281,7 @@ logic_grounding_stage(ReqId, Props, Inventory, Context, Stage) :-
|
|
|
224
281
|
InvalidRuleFactIds,
|
|
225
282
|
Status
|
|
226
283
|
),
|
|
284
|
+
ground_source_refs(Evidence, GroundSources),
|
|
227
285
|
Stage = _{
|
|
228
286
|
status: Status,
|
|
229
287
|
manifestClaims: ManifestKeys,
|
|
@@ -238,7 +296,8 @@ logic_grounding_stage(ReqId, Props, Inventory, Context, Stage) :-
|
|
|
238
296
|
missingManifestClaims: MissingManifestKeys,
|
|
239
297
|
extraManifestClaims: ExtraManifestKeys,
|
|
240
298
|
undeclaredGroundClaims: UndeclaredGroundKeys,
|
|
241
|
-
invalidRuleFacts: InvalidRuleFactIds
|
|
299
|
+
invalidRuleFacts: InvalidRuleFactIds,
|
|
300
|
+
sources: GroundSources
|
|
242
301
|
}.
|
|
243
302
|
|
|
244
303
|
requirement_claim_keys(Props, true, Keys) :-
|
|
@@ -432,56 +491,213 @@ violation_for_entity(Violations, EntityId) :-
|
|
|
432
491
|
|
|
433
492
|
scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
434
493
|
findall(ScenarioId,
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
sort(
|
|
438
|
-
(
|
|
439
|
-
|
|
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),
|
|
502
|
+
maplist(entity_source_ref, ScenarioIds, 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, _).
|
|
440
513
|
|
|
441
514
|
scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
|
|
515
|
+
maplist(scenario_test_obligation, ScenarioIds, Obligations),
|
|
442
516
|
findall(TestId,
|
|
443
|
-
(member(
|
|
517
|
+
(member(Obligation, Obligations), member(TestId, Obligation.tests)),
|
|
444
518
|
ScenarioTests0),
|
|
445
519
|
sort(ScenarioTests0, ScenarioTests),
|
|
446
|
-
(
|
|
447
|
-
|
|
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
|
+
),
|
|
534
|
+
maplist(entity_source_ref, ScenarioTests, 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
|
+
}.
|
|
448
544
|
|
|
449
|
-
|
|
450
|
-
|
|
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) :-
|
|
451
572
|
kb_entity(TestId, test, _).
|
|
573
|
+
|
|
452
574
|
scenario_test(ScenarioId, TestId) :-
|
|
453
|
-
|
|
454
|
-
|
|
575
|
+
declared_scenario_test_target(ScenarioId, TestId),
|
|
576
|
+
existing_test(TestId).
|
|
455
577
|
|
|
456
|
-
passing_e2e_stage(
|
|
457
|
-
maplist(
|
|
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),
|
|
458
582
|
evidence_tests_with_state(Evidence, passed, PassingE2eTests),
|
|
459
583
|
evidence_tests_with_state(Evidence, missing, MissingReceiptTests),
|
|
460
584
|
evidence_tests_with_state(Evidence, stale, StaleReceiptTests),
|
|
461
585
|
evidence_tests_with_state(Evidence, failed, FailedReceiptTests),
|
|
462
586
|
evidence_tests_with_state(Evidence, invalid, InvalidReceiptTests),
|
|
587
|
+
evidence_tests_with_state(Evidence, contract_mismatch, ContractMismatchReceiptTests),
|
|
463
588
|
evidence_tests_with_state(Evidence, snapshot_unavailable, SnapshotUnavailableTests),
|
|
464
589
|
evidence_tests_with_state(Evidence, not_end_to_end, NonEndToEndTests),
|
|
465
|
-
passing_e2e_status(
|
|
590
|
+
passing_e2e_status(ScenarioObligations, Status),
|
|
466
591
|
Stage = _{
|
|
467
592
|
status: Status,
|
|
468
593
|
tests: PassingE2eTests,
|
|
594
|
+
scenarioObligations: ScenarioObligations,
|
|
469
595
|
receiptEvidence: Evidence,
|
|
470
596
|
missingReceiptTests: MissingReceiptTests,
|
|
471
597
|
staleReceiptTests: StaleReceiptTests,
|
|
472
598
|
failedReceiptTests: FailedReceiptTests,
|
|
473
599
|
invalidReceiptTests: InvalidReceiptTests,
|
|
600
|
+
contractMismatchReceiptTests: ContractMismatchReceiptTests,
|
|
474
601
|
snapshotUnavailableTests: SnapshotUnavailableTests,
|
|
475
602
|
nonEndToEndTests: NonEndToEndTests,
|
|
476
|
-
currentCodeSnapshot: Context.
|
|
477
|
-
checkedAt: Context.
|
|
478
|
-
maxAgeSeconds: Context.
|
|
603
|
+
currentCodeSnapshot: Context.proofSnapshot,
|
|
604
|
+
checkedAt: Context.proofCheckedAt,
|
|
605
|
+
maxAgeSeconds: Context.proofMaxAgeSeconds
|
|
479
606
|
}.
|
|
480
607
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
|
655
|
+
}.
|
|
656
|
+
|
|
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).
|
|
485
701
|
|
|
486
702
|
evidence_tests_with_state(Evidence, State, TestIds) :-
|
|
487
703
|
findall(TestId,
|
|
@@ -494,72 +710,150 @@ test_receipt_evidence(Context, TestId, Evidence) :-
|
|
|
494
710
|
test_scope(Props, Scope),
|
|
495
711
|
( Scope \= end_to_end
|
|
496
712
|
-> Evidence = _{testId: TestId, state: not_end_to_end, scope: Scope}
|
|
497
|
-
; Context.
|
|
713
|
+
; Context.proofSnapshot == unknown
|
|
498
714
|
-> Evidence = _{testId: TestId, state: snapshot_unavailable, scope: Scope}
|
|
499
|
-
;
|
|
500
|
-
receipt_evidence_state(TestId, Scope, Receipts, ReceiptPropertyPresent, Context, Evidence)
|
|
715
|
+
; proof_receipt_entries(Props, Receipts, ReceiptPropertyPresent),
|
|
716
|
+
receipt_evidence_state(TestId, Scope, Props, Receipts, ReceiptPropertyPresent, Context, Evidence)
|
|
501
717
|
).
|
|
502
718
|
|
|
503
|
-
receipt_evidence_state(TestId, Scope, [], false, _Context,
|
|
719
|
+
receipt_evidence_state(TestId, Scope, _Props, [], false, _Context,
|
|
504
720
|
_{testId: TestId, state: missing, scope: Scope, receiptCount: 0}) :- !.
|
|
505
|
-
receipt_evidence_state(TestId, Scope, Receipts, _Present, _Context,
|
|
721
|
+
receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, _Context,
|
|
506
722
|
_{testId: TestId, state: invalid, scope: Scope, receiptCount: Count}) :-
|
|
507
723
|
length(Receipts, Count),
|
|
508
|
-
(
|
|
509
|
-
|
|
724
|
+
proof_contract_binding(Props, ContractBinding),
|
|
725
|
+
(ContractBinding == invalid
|
|
726
|
+
; Receipts == []
|
|
727
|
+
; member(Receipt, Receipts), \+ valid_receipt_shape(TestId, Receipt)
|
|
510
728
|
; \+ chronological_receipt_history(Receipts)),
|
|
511
729
|
!.
|
|
512
|
-
receipt_evidence_state(TestId, Scope, Receipts, _Present, Context, Evidence) :-
|
|
513
|
-
|
|
514
|
-
(
|
|
730
|
+
receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Evidence) :-
|
|
731
|
+
receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts),
|
|
732
|
+
( SnapshotReceipts == []
|
|
515
733
|
-> length(Receipts, Count),
|
|
516
734
|
Evidence = _{testId: TestId, state: stale, scope: Scope, receiptCount: Count}
|
|
517
|
-
;
|
|
518
|
-
|
|
519
|
-
|
|
735
|
+
; proof_contract_binding(Props, ContractBinding),
|
|
736
|
+
include(receipt_matches_current_binding(Scope, ContractBinding), SnapshotReceipts, CurrentReceipts),
|
|
737
|
+
( CurrentReceipts == []
|
|
738
|
+
-> receipt_contract_mismatch_evidence(TestId, Scope, SnapshotReceipts, ContractBinding, Evidence)
|
|
739
|
+
; latest_receipt(CurrentReceipts, Latest, FinishedStamp),
|
|
740
|
+
receipt_runtime_state(Latest, FinishedStamp, Context, State, AgeSeconds),
|
|
741
|
+
receipt_evidence_dict(TestId, Scope, Latest, State, AgeSeconds, Evidence)
|
|
742
|
+
)
|
|
520
743
|
).
|
|
521
744
|
|
|
522
|
-
|
|
523
|
-
( memberchk(
|
|
745
|
+
proof_receipt_entries(Props, Receipts, Present) :-
|
|
746
|
+
( memberchk(proof_receipts=Raw, Props)
|
|
524
747
|
-> Present = true,
|
|
525
748
|
inventory_entries(Raw, Receipts)
|
|
526
749
|
; Present = false,
|
|
527
750
|
Receipts = []
|
|
528
751
|
).
|
|
529
752
|
|
|
530
|
-
valid_receipt_shape(TestId,
|
|
753
|
+
valid_receipt_shape(TestId, Receipt) :-
|
|
531
754
|
inventory_entry_field(Receipt, version, RawVersion),
|
|
532
|
-
normalize_receipt_atom(RawVersion, 'kibi.
|
|
755
|
+
normalize_receipt_atom(RawVersion, 'kibi.proof-receipt.v1'),
|
|
533
756
|
inventory_entry_field(Receipt, receipt_id, RawReceiptId),
|
|
534
757
|
normalize_receipt_atom(RawReceiptId, ReceiptId), valid_receipt_id(ReceiptId),
|
|
535
758
|
inventory_entry_field(Receipt, test_id, RawTestId),
|
|
536
759
|
normalize_receipt_atom(RawTestId, TestId),
|
|
537
|
-
inventory_entry_field(Receipt, runner, RawRunner),
|
|
538
|
-
normalize_receipt_atom(RawRunner, Runner), Runner \= '',
|
|
539
|
-
inventory_entry_field(Receipt, command, RawCommand),
|
|
540
|
-
normalize_receipt_atom(RawCommand, Command), Command \= '',
|
|
541
760
|
inventory_entry_field(Receipt, scope, RawScope),
|
|
542
|
-
normalize_receipt_atom(RawScope,
|
|
761
|
+
normalize_receipt_atom(RawScope, ReceiptScope),
|
|
762
|
+
memberchk(ReceiptScope, [unit, integration, end_to_end]),
|
|
543
763
|
inventory_entry_field(Receipt, outcome, RawOutcome),
|
|
544
764
|
normalize_receipt_atom(RawOutcome, Outcome),
|
|
545
|
-
memberchk(Outcome, [passed, failed, errored, cancelled, skipped]),
|
|
765
|
+
memberchk(Outcome, [passed, failed, errored, cancelled, skipped, timed_out, interrupted]),
|
|
546
766
|
inventory_entry_field(Receipt, code_snapshot, RawSnapshot),
|
|
547
767
|
normalize_receipt_atom(RawSnapshot, Snapshot), valid_sha256(Snapshot),
|
|
548
768
|
inventory_entry_field(Receipt, environment_hash, RawEnvironmentHash),
|
|
549
769
|
normalize_receipt_atom(RawEnvironmentHash, EnvironmentHash), valid_sha256(EnvironmentHash),
|
|
550
770
|
inventory_entry_field(Receipt, artifact_digest, RawArtifactDigest),
|
|
551
771
|
normalize_receipt_atom(RawArtifactDigest, ArtifactDigest), valid_sha256(ArtifactDigest),
|
|
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 \= '',
|
|
781
|
+
inventory_entry_field(Receipt, command_argv, RawCommandArgv),
|
|
782
|
+
is_list(RawCommandArgv),
|
|
783
|
+
RawCommandArgv \= [],
|
|
784
|
+
maplist(nonempty_receipt_atom, RawCommandArgv),
|
|
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),
|
|
552
796
|
receipt_timestamp(Receipt, started_at, StartedStamp),
|
|
553
797
|
receipt_timestamp(Receipt, finished_at, FinishedStamp),
|
|
554
798
|
FinishedStamp >= StartedStamp.
|
|
555
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
|
+
!.
|
|
811
|
+
|
|
812
|
+
nonempty_receipt_atom(Value) :-
|
|
813
|
+
normalize_receipt_atom(Value, Atom),
|
|
814
|
+
Atom \= ''.
|
|
815
|
+
|
|
816
|
+
valid_receipt_proof_result(Result) :-
|
|
817
|
+
inventory_entry_field(Result, symbol_id, RawSymbolId),
|
|
818
|
+
nonempty_receipt_atom(RawSymbolId),
|
|
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
|
+
!.
|
|
849
|
+
|
|
556
850
|
valid_sha256(Value) :-
|
|
557
851
|
atom_length(Value, 64),
|
|
558
852
|
atom_chars(Value, Chars),
|
|
559
853
|
forall(member(Char, Chars), memberchk(Char, ['0','1','2','3','4','5','6','7','8','9',a,b,c,d,e,f])).
|
|
560
854
|
|
|
561
855
|
valid_receipt_id(Value) :-
|
|
562
|
-
re_match('^
|
|
856
|
+
re_match('^PR-[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$', Value).
|
|
563
857
|
|
|
564
858
|
receipt_timestamp(Receipt, Field, Stamp) :-
|
|
565
859
|
inventory_entry_field(Receipt, Field, RawValue),
|
|
@@ -584,6 +878,105 @@ receipt_for_snapshot(Snapshot, Receipt) :-
|
|
|
584
878
|
inventory_entry_field(Receipt, code_snapshot, RawSnapshot),
|
|
585
879
|
normalize_receipt_atom(RawSnapshot, Snapshot).
|
|
586
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
|
+
|
|
904
|
+
receipt_matches_current_binding(Scope, ContractBinding, Receipt) :-
|
|
905
|
+
inventory_entry_field(Receipt, scope, RawScope),
|
|
906
|
+
normalize_receipt_atom(RawScope, Scope),
|
|
907
|
+
receipt_matches_current_contract(ContractBinding, Receipt).
|
|
908
|
+
|
|
909
|
+
receipt_matches_current_contract(none, _Receipt).
|
|
910
|
+
receipt_matches_current_contract(hash(ExpectedHash), Receipt) :-
|
|
911
|
+
inventory_entry_field(Receipt, version, RawVersion),
|
|
912
|
+
normalize_receipt_atom(RawVersion, 'kibi.proof-receipt.v1'),
|
|
913
|
+
inventory_entry_field(Receipt, contract_hash, RawContractHash),
|
|
914
|
+
normalize_receipt_atom(RawContractHash, ExpectedHash).
|
|
915
|
+
|
|
916
|
+
receipt_contract_mismatch_evidence(TestId, Scope, Receipts, ContractBinding, Evidence) :-
|
|
917
|
+
length(Receipts, Count),
|
|
918
|
+
findall(Hash,
|
|
919
|
+
(member(Receipt, Receipts),
|
|
920
|
+
inventory_entry_field(Receipt, contract_hash, RawHash),
|
|
921
|
+
normalize_receipt_atom(RawHash, Hash)),
|
|
922
|
+
Hashes0),
|
|
923
|
+
sort(Hashes0, Hashes),
|
|
924
|
+
current_contract_hash_value(ContractBinding, CurrentContractHash),
|
|
925
|
+
Evidence = _{
|
|
926
|
+
testId: TestId,
|
|
927
|
+
state: contract_mismatch,
|
|
928
|
+
scope: Scope,
|
|
929
|
+
receiptCount: Count,
|
|
930
|
+
currentContractHash: CurrentContractHash,
|
|
931
|
+
receiptContractHashes: Hashes
|
|
932
|
+
}.
|
|
933
|
+
|
|
934
|
+
current_contract_hash_value(hash(Hash), Hash).
|
|
935
|
+
current_contract_hash_value(none, none).
|
|
936
|
+
|
|
937
|
+
proof_contract_binding(Props, Binding) :-
|
|
938
|
+
( memberchk(proof_contract=RawContract, Props)
|
|
939
|
+
-> (proof_contract_hash(RawContract, Hash) -> Binding = hash(Hash) ; Binding = invalid)
|
|
940
|
+
; Binding = none
|
|
941
|
+
).
|
|
942
|
+
|
|
943
|
+
proof_contract_hash(RawContract, Hash) :-
|
|
944
|
+
proof_contract_dict(RawContract, Contract),
|
|
945
|
+
canonical_json_value(Contract, Canonical),
|
|
946
|
+
crypto_data_hash(Canonical, Hash, [algorithm(sha256), encoding(utf8)]).
|
|
947
|
+
|
|
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) :-
|
|
955
|
+
is_dict(Contract), !.
|
|
956
|
+
proof_contract_dict(Raw, Contract) :-
|
|
957
|
+
(atom(Raw) ; string(Raw)),
|
|
958
|
+
catch(atom_json_dict(Raw, Contract, [value_string_as(string)]), _, fail),
|
|
959
|
+
is_dict(Contract).
|
|
960
|
+
|
|
961
|
+
canonical_json_value(Value, Json) :-
|
|
962
|
+
is_dict(Value), !,
|
|
963
|
+
dict_pairs(Value, _, Pairs),
|
|
964
|
+
maplist(canonical_json_pair, Pairs, Parts),
|
|
965
|
+
atomics_to_string(Parts, ",", Body),
|
|
966
|
+
format(string(Json), "{~s}", [Body]).
|
|
967
|
+
canonical_json_value(Value, Json) :-
|
|
968
|
+
is_list(Value), !,
|
|
969
|
+
maplist(canonical_json_value, Value, Parts),
|
|
970
|
+
atomics_to_string(Parts, ",", Body),
|
|
971
|
+
format(string(Json), "[~s]", [Body]).
|
|
972
|
+
canonical_json_value(Value, Json) :-
|
|
973
|
+
with_output_to(string(Json), json_write(current_output, Value, [width(0)])).
|
|
974
|
+
|
|
975
|
+
canonical_json_pair(Key-Value, Json) :-
|
|
976
|
+
with_output_to(string(KeyJson), json_write(current_output, Key, [width(0)])),
|
|
977
|
+
canonical_json_value(Value, ValueJson),
|
|
978
|
+
format(string(Json), "~s:~s", [KeyJson, ValueJson]).
|
|
979
|
+
|
|
587
980
|
latest_receipt(Receipts, Latest, FinishedStamp) :-
|
|
588
981
|
findall(Stamp-Receipt,
|
|
589
982
|
(member(Receipt, Receipts), receipt_timestamp(Receipt, finished_at, Stamp)),
|
|
@@ -592,23 +985,21 @@ latest_receipt(Receipts, Latest, FinishedStamp) :-
|
|
|
592
985
|
last(Sorted, FinishedStamp-Latest).
|
|
593
986
|
|
|
594
987
|
receipt_runtime_state(_Receipt, FinishedStamp, Context, invalid, AgeSeconds) :-
|
|
595
|
-
AgeSeconds is Context.
|
|
988
|
+
AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
|
|
596
989
|
AgeSeconds < -300,
|
|
597
990
|
!.
|
|
598
991
|
receipt_runtime_state(_Receipt, FinishedStamp, Context, stale, AgeSeconds) :-
|
|
599
|
-
AgeSeconds is Context.
|
|
600
|
-
AgeSeconds > Context.
|
|
992
|
+
AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
|
|
993
|
+
AgeSeconds > Context.proofMaxAgeSeconds,
|
|
601
994
|
!.
|
|
602
995
|
receipt_runtime_state(Receipt, FinishedStamp, Context, State, AgeSeconds) :-
|
|
603
|
-
AgeSeconds is Context.
|
|
996
|
+
AgeSeconds is Context.proofCheckedAtStamp - FinishedStamp,
|
|
604
997
|
inventory_entry_field(Receipt, outcome, RawOutcome),
|
|
605
998
|
normalize_receipt_atom(RawOutcome, Outcome),
|
|
606
999
|
(Outcome == passed -> State = passed ; State = failed).
|
|
607
1000
|
|
|
608
1001
|
receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
|
|
609
1002
|
receipt_string_field(Receipt, receipt_id, ReceiptId),
|
|
610
|
-
receipt_string_field(Receipt, runner, Runner),
|
|
611
|
-
receipt_string_field(Receipt, command, Command),
|
|
612
1003
|
receipt_string_field(Receipt, outcome, Outcome),
|
|
613
1004
|
receipt_string_field(Receipt, code_snapshot, CodeSnapshot),
|
|
614
1005
|
receipt_string_field(Receipt, environment_hash, EnvironmentHash),
|
|
@@ -620,8 +1011,6 @@ receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
|
|
|
620
1011
|
state: State,
|
|
621
1012
|
scope: Scope,
|
|
622
1013
|
receiptId: ReceiptId,
|
|
623
|
-
runner: Runner,
|
|
624
|
-
command: Command,
|
|
625
1014
|
outcome: Outcome,
|
|
626
1015
|
codeSnapshot: CodeSnapshot,
|
|
627
1016
|
environmentHash: EnvironmentHash,
|
|
@@ -662,7 +1051,7 @@ test_scope(Props, end_to_end) :-
|
|
|
662
1051
|
!.
|
|
663
1052
|
test_scope(_, unknown).
|
|
664
1053
|
|
|
665
|
-
executable_symbol_stage([], _{status: blocked, symbols: [], missingTests: []}, []).
|
|
1054
|
+
executable_symbol_stage([], _{status: blocked, symbols: [], missingTests: [], coordinates: []}, []).
|
|
666
1055
|
executable_symbol_stage(PassingE2eTests, Stage, Symbols) :-
|
|
667
1056
|
PassingE2eTests \= [],
|
|
668
1057
|
findall(SymbolId,
|
|
@@ -673,7 +1062,8 @@ executable_symbol_stage(PassingE2eTests, Stage, Symbols) :-
|
|
|
673
1062
|
sort(Symbols0, Symbols),
|
|
674
1063
|
include(test_missing_executable_symbol, PassingE2eTests, MissingTests),
|
|
675
1064
|
(MissingTests = [] -> Status = passed ; Status = missing),
|
|
676
|
-
|
|
1065
|
+
maplist(symbol_coordinate_ref, Symbols, Coordinates),
|
|
1066
|
+
Stage = _{status: Status, symbols: Symbols, missingTests: MissingTests, coordinates: Coordinates}.
|
|
677
1067
|
|
|
678
1068
|
test_missing_executable_symbol(TestId) :-
|
|
679
1069
|
\+ (kb_relationship(executable_for, SymbolId, TestId), kb_entity(SymbolId, symbol, _)).
|
|
@@ -683,16 +1073,56 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
|
|
|
683
1073
|
(kb_relationship(implements, SymbolId, ReqId),
|
|
684
1074
|
kb_entity(SymbolId, symbol, _),
|
|
685
1075
|
\+ kb:executable_test_symbol(SymbolId)),
|
|
686
|
-
|
|
687
|
-
sort(
|
|
1076
|
+
Implementing0),
|
|
1077
|
+
sort(Implementing0, ImplementingSymbols),
|
|
1078
|
+
include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
|
|
1079
|
+
exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
|
|
688
1080
|
include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredSymbols),
|
|
689
|
-
production_stage_status(ProductionSymbols, PassingE2eTests, UncoveredSymbols, Status),
|
|
690
|
-
|
|
1081
|
+
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status, StatusReason),
|
|
1082
|
+
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
|
|
1083
|
+
StageBase = _{
|
|
1084
|
+
status: Status,
|
|
1085
|
+
symbols: ProductionSymbols,
|
|
1086
|
+
structuralSymbols: StructuralSymbols,
|
|
1087
|
+
uncoveredSymbols: UncoveredSymbols,
|
|
1088
|
+
coordinates: Coordinates
|
|
1089
|
+
},
|
|
1090
|
+
( Status == passed
|
|
1091
|
+
-> Stage = StageBase
|
|
1092
|
+
; put_dict(reason, StageBase, StatusReason, Stage)
|
|
1093
|
+
).
|
|
691
1094
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1095
|
+
type_shape_symbol_with_structural_contract(SymbolId) :-
|
|
1096
|
+
kb_entity(SymbolId, symbol, SymbolProps),
|
|
1097
|
+
memberchk(symbol_role=RawRole, SymbolProps),
|
|
1098
|
+
normalize_atom(RawRole, 'type-shape'),
|
|
1099
|
+
kb_relationship(covered_by, SymbolId, TestId),
|
|
1100
|
+
kb_entity(TestId, test, TestProps),
|
|
1101
|
+
test_scope(TestProps, unit),
|
|
1102
|
+
memberchk(status=RawStatus, TestProps),
|
|
1103
|
+
normalize_atom(RawStatus, Status),
|
|
1104
|
+
memberchk(Status, [active, passing]).
|
|
1105
|
+
|
|
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
|
+
).
|
|
696
1126
|
|
|
697
1127
|
symbol_not_covered_by_tests(Tests, SymbolId) :-
|
|
698
1128
|
\+ (member(TestId, Tests), kb_relationship(covered_by, SymbolId, TestId)).
|
|
@@ -707,11 +1137,15 @@ source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, Stage) :
|
|
|
707
1137
|
; RequirementSource = present,
|
|
708
1138
|
coordinate_stage_status(Symbols, MissingSymbols, Status)
|
|
709
1139
|
),
|
|
1140
|
+
requirement_source_path(ReqProps, RequirementPath),
|
|
1141
|
+
maplist(symbol_coordinate_ref, Symbols, Coordinates),
|
|
710
1142
|
Stage = _{
|
|
711
1143
|
status: Status,
|
|
712
1144
|
requirementSource: RequirementSource,
|
|
1145
|
+
requirementPath: RequirementPath,
|
|
713
1146
|
symbols: Symbols,
|
|
714
|
-
missingSymbols: MissingSymbols
|
|
1147
|
+
missingSymbols: MissingSymbols,
|
|
1148
|
+
coordinates: Coordinates
|
|
715
1149
|
}.
|
|
716
1150
|
|
|
717
1151
|
coordinate_stage_status([], _, blocked) :- !.
|
|
@@ -724,7 +1158,7 @@ symbol_missing_coordinates(SymbolId) :-
|
|
|
724
1158
|
|
|
725
1159
|
valid_symbol_coordinates(Props) :-
|
|
726
1160
|
memberchk(sourceFile=RawSourceFile, Props),
|
|
727
|
-
|
|
1161
|
+
source_path_atom(RawSourceFile, SourceFile),
|
|
728
1162
|
SourceFile \= '',
|
|
729
1163
|
memberchk(sourceLine=RawLine, Props),
|
|
730
1164
|
memberchk(sourceColumn=RawColumn, Props),
|
|
@@ -739,10 +1173,66 @@ valid_symbol_coordinates(Props) :-
|
|
|
739
1173
|
EndLine >= Line,
|
|
740
1174
|
EndColumn >= 0.
|
|
741
1175
|
|
|
742
|
-
|
|
1176
|
+
% Prefer sourceFile, then source, without URI-basename stripping. Paths must
|
|
1177
|
+
% remain inspectable coordinates for report linking.
|
|
1178
|
+
proof_source_property(Props, RawSource) :-
|
|
1179
|
+
memberchk(sourceFile=RawSource, Props),
|
|
1180
|
+
source_path_atom(RawSource, _),
|
|
1181
|
+
!.
|
|
1182
|
+
proof_source_property(Props, RawSource) :-
|
|
743
1183
|
memberchk(source=RawSource, Props),
|
|
744
|
-
|
|
745
|
-
|
|
1184
|
+
source_path_atom(RawSource, _).
|
|
1185
|
+
|
|
1186
|
+
nonempty_source(Props) :-
|
|
1187
|
+
proof_source_property(Props, _).
|
|
1188
|
+
|
|
1189
|
+
requirement_source_path(Props, Path) :-
|
|
1190
|
+
proof_source_property(Props, RawSource),
|
|
1191
|
+
source_path_atom(RawSource, Path),
|
|
1192
|
+
!.
|
|
1193
|
+
requirement_source_path(_, '').
|
|
1194
|
+
|
|
1195
|
+
entity_source_path(Id, Path) :-
|
|
1196
|
+
kb_entity(Id, _, Props),
|
|
1197
|
+
requirement_source_path(Props, Path).
|
|
1198
|
+
|
|
1199
|
+
entity_source_ref(Id, _{id: Id, path: Path}) :-
|
|
1200
|
+
entity_source_path(Id, Path).
|
|
1201
|
+
|
|
1202
|
+
ground_source_refs(Evidence, Refs) :-
|
|
1203
|
+
findall(Ref,
|
|
1204
|
+
(member(Item, Evidence),
|
|
1205
|
+
entity_source_ref(Item.factId, Ref),
|
|
1206
|
+
Ref.path \= ''),
|
|
1207
|
+
Refs0),
|
|
1208
|
+
sort(Refs0, Refs).
|
|
1209
|
+
|
|
1210
|
+
symbol_coordinate_ref(SymbolId, Ref) :-
|
|
1211
|
+
kb_entity(SymbolId, symbol, Props),
|
|
1212
|
+
memberchk(sourceFile=RawFile, Props),
|
|
1213
|
+
source_path_atom(RawFile, File),
|
|
1214
|
+
File \= '',
|
|
1215
|
+
!,
|
|
1216
|
+
( valid_symbol_coordinates(Props)
|
|
1217
|
+
-> memberchk(sourceLine=RawLine, Props),
|
|
1218
|
+
memberchk(sourceColumn=RawColumn, Props),
|
|
1219
|
+
memberchk(sourceEndLine=RawEndLine, Props),
|
|
1220
|
+
memberchk(sourceEndColumn=RawEndColumn, Props),
|
|
1221
|
+
normalize_integer(RawLine, Line),
|
|
1222
|
+
normalize_integer(RawColumn, Column),
|
|
1223
|
+
normalize_integer(RawEndLine, EndLine),
|
|
1224
|
+
normalize_integer(RawEndColumn, EndColumn),
|
|
1225
|
+
Ref = _{
|
|
1226
|
+
id: SymbolId,
|
|
1227
|
+
path: File,
|
|
1228
|
+
line: Line,
|
|
1229
|
+
column: Column,
|
|
1230
|
+
endLine: EndLine,
|
|
1231
|
+
endColumn: EndColumn
|
|
1232
|
+
}
|
|
1233
|
+
; Ref = _{id: SymbolId, path: File}
|
|
1234
|
+
).
|
|
1235
|
+
symbol_coordinate_ref(SymbolId, _{id: SymbolId, path: ''}).
|
|
746
1236
|
|
|
747
1237
|
proof_status(Stages, unresolved) :-
|
|
748
1238
|
Stages.contradictions.status == blocked,
|
|
@@ -762,7 +1252,36 @@ stage_dict(Stages, Stage) :-
|
|
|
762
1252
|
member(_-Stage, Pairs).
|
|
763
1253
|
|
|
764
1254
|
proof_gaps(Stages, Gaps) :-
|
|
765
|
-
|
|
1255
|
+
proof_issues(Stages, Gaps, _).
|
|
1256
|
+
|
|
1257
|
+
proof_advisories(Stages, Advisories) :-
|
|
1258
|
+
proof_issues(Stages, _, Advisories).
|
|
1259
|
+
|
|
1260
|
+
% implements REQ-kibi-conservative-requirement-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.
|
|
1264
|
+
% Invariant: proofStatus == proven implies proofGaps == [].
|
|
1265
|
+
proof_issues(Stages, Gaps, Advisories) :-
|
|
1266
|
+
findall(Gap,
|
|
1267
|
+
(gap_definition(Gap, _, _, _),
|
|
1268
|
+
proof_gap_present(Gap, Stages),
|
|
1269
|
+
\+ proof_issue_advisory(Gap, Stages)),
|
|
1270
|
+
Gaps),
|
|
1271
|
+
findall(Gap,
|
|
1272
|
+
(gap_definition(Gap, _, _, _),
|
|
1273
|
+
proof_gap_present(Gap, Stages),
|
|
1274
|
+
proof_issue_advisory(Gap, Stages)),
|
|
1275
|
+
Advisories).
|
|
1276
|
+
|
|
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).
|
|
1282
|
+
|
|
1283
|
+
proof_issue_advisory(_Gap, _Stages) :-
|
|
1284
|
+
fail.
|
|
766
1285
|
|
|
767
1286
|
proof_gap_present(missing_semantic_inventory, Stages) :- Stages.semanticInventory.propositionCount =:= 0.
|
|
768
1287
|
proof_gap_present(incomplete_semantic_inventory, Stages) :- Stages.semanticInventory.missingCount > 0.
|
|
@@ -779,13 +1298,16 @@ proof_gap_present(contradiction_check_incomplete, Stages) :- Stages.contradictio
|
|
|
779
1298
|
proof_gap_present(missing_scenario, Stages) :- Stages.scenarios.status == missing.
|
|
780
1299
|
proof_gap_present(missing_scenario_test, Stages) :- Stages.scenarios.status == passed, Stages.scenarioTests.status == missing.
|
|
781
1300
|
proof_gap_present(missing_passing_e2e, Stages) :- Stages.passingE2e.status == missing.
|
|
782
|
-
proof_gap_present(
|
|
783
|
-
proof_gap_present(
|
|
784
|
-
proof_gap_present(
|
|
785
|
-
proof_gap_present(
|
|
786
|
-
proof_gap_present(
|
|
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 \= [].
|
|
787
1307
|
proof_gap_present(missing_executable_test_symbol, Stages) :- Stages.executableSymbols.status == missing.
|
|
788
|
-
proof_gap_present(missing_production_symbol, Stages) :-
|
|
1308
|
+
proof_gap_present(missing_production_symbol, Stages) :-
|
|
1309
|
+
Stages.productionSymbols.symbols == [],
|
|
1310
|
+
Stages.productionSymbols.structuralSymbols == [].
|
|
789
1311
|
proof_gap_present(missing_production_symbol_coverage, Stages) :- Stages.productionSymbols.uncoveredSymbols \= [].
|
|
790
1312
|
proof_gap_present(missing_symbol_coordinates, Stages) :- Stages.sourceCoordinates.missingSymbols \= [].
|
|
791
1313
|
proof_gap_present(missing_requirement_source, Stages) :- Stages.sourceCoordinates.requirementSource == missing.
|
|
@@ -803,14 +1325,15 @@ gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete log
|
|
|
803
1325
|
gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
|
|
804
1326
|
gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
|
|
805
1327
|
gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
|
|
806
|
-
gap_definition(
|
|
807
|
-
gap_definition(
|
|
808
|
-
gap_definition(
|
|
809
|
-
gap_definition(
|
|
810
|
-
gap_definition(
|
|
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.").
|
|
811
1334
|
gap_definition(missing_executable_test_symbol, 60, executable_symbols, "Link executable test code to every qualifying E2E test with executable_for.").
|
|
812
1335
|
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.").
|
|
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.").
|
|
814
1337
|
gap_definition(missing_symbol_coordinates, 80, source_coordinates, "Refresh and persist exact coordinates for every proof-bearing symbol.").
|
|
815
1338
|
gap_definition(missing_requirement_source, 81, source_coordinates, "Bind the requirement to its current source document.").
|
|
816
1339
|
|
|
@@ -822,6 +1345,10 @@ proof_repairs(Gaps, Repairs) :-
|
|
|
822
1345
|
normalize_atom(Value, Atom) :-
|
|
823
1346
|
kb:normalize_term_atom(Value, Atom).
|
|
824
1347
|
|
|
1348
|
+
source_path_atom(Value, Atom) :-
|
|
1349
|
+
kb:source_value_atom(Value, Atom),
|
|
1350
|
+
Atom \= ''.
|
|
1351
|
+
|
|
825
1352
|
normalize_atom_list(Value, Atoms) :-
|
|
826
1353
|
kb:normalize_term_atom_list(Value, Atoms).
|
|
827
1354
|
|