kibi-core 0.22.5 → 0.24.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 +1 -1
- package/schema/entities.pl +6 -0
- package/schema/validation.pl +4 -1
- package/src/certificate_check.pl +783 -0
- package/src/certificates.pl +245 -0
- package/src/checks.pl +23 -5
- package/src/claim_obligations.pl +378 -0
- package/src/code_certificates.pl +297 -0
- package/src/discovery.pl +176 -9
- package/src/intervals.pl +11 -0
- package/src/kb.pl +7 -0
- package/src/kb_consistency.pl +213 -0
- package/src/requirement_proof.pl +628 -33
- package/src/rule_registry.pl +20 -0
- package/src/semantic_quality.pl +1 -1
package/src/requirement_proof.pl
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
requirement_proof_context/1,
|
|
6
6
|
requirement_proof_context/4,
|
|
7
7
|
requirement_proof_context/6,
|
|
8
|
-
requirement_proof/4
|
|
8
|
+
requirement_proof/4,
|
|
9
|
+
test_receipt_evidence/4
|
|
9
10
|
]).
|
|
10
11
|
|
|
11
12
|
% Source contains non-ASCII text; do not depend on the host locale.
|
|
@@ -24,8 +25,10 @@
|
|
|
24
25
|
check_rule_verifiability/1
|
|
25
26
|
]).
|
|
26
27
|
:- use_module('logic_ir.pl', [logic_rule_safety/2]).
|
|
28
|
+
:- use_module('code_certificates.pl', [code_certificate_check/2, code_certificate_digest/2]).
|
|
29
|
+
:- use_module('claim_obligations.pl', [claim_obligation/3, obligation_equal/2]).
|
|
27
30
|
|
|
28
|
-
proof_version('kibi.requirement-proof.
|
|
31
|
+
proof_version('kibi.requirement-proof.v5').
|
|
29
32
|
|
|
30
33
|
requirement_proof_context(Context) :-
|
|
31
34
|
requirement_proof_context(unknown, '1970-01-01T00:00:00Z', 604800, Context).
|
|
@@ -71,12 +74,12 @@ requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
|
71
74
|
kb:current_req(ReqId),
|
|
72
75
|
proof_exempt(ReqProps, Reason),
|
|
73
76
|
!,
|
|
74
|
-
exempt_requirement_proof(Reason, Proof).
|
|
75
|
-
requirement_proof(ReqId,
|
|
77
|
+
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
78
|
+
requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
76
79
|
\+ kb:current_req(ReqId),
|
|
77
80
|
!,
|
|
78
81
|
applicability_exclusion_reason(ReqId, Reason),
|
|
79
|
-
exempt_requirement_proof(Reason, Proof).
|
|
82
|
+
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
80
83
|
requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
81
84
|
semantic_inventory_stage(ReqProps, SemanticStage, Inventory),
|
|
82
85
|
logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
|
|
@@ -101,22 +104,48 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
|
101
104
|
proof_issues(Stages, Gaps, Advisories),
|
|
102
105
|
proof_repairs(Gaps, Repairs),
|
|
103
106
|
proof_status(Stages, Status),
|
|
107
|
+
certificate_rejections(ScenarioTestStage, Context, Rejections),
|
|
108
|
+
claim_evidence(ReqId, ReqProps, Inventory, Status, ScenarioTestStage, Context,
|
|
109
|
+
ClaimEvidence, ProvedTests, BindingRejections),
|
|
110
|
+
requirement_evidence(ReqProps, Status, PassingE2eTests, ProvedTests, EvidenceLevel, Evidence),
|
|
111
|
+
evidence_floor(ReqProps, EvidenceLevel, Gaps, Stages, EvidenceFloor),
|
|
112
|
+
claim_summary(ClaimEvidence, ClaimSummary, UnprovedClaims),
|
|
104
113
|
proof_version(Version),
|
|
105
|
-
|
|
114
|
+
Proof0 = _{
|
|
106
115
|
proofVersion: Version,
|
|
107
116
|
proofStatus: Status,
|
|
117
|
+
evidenceLevel: EvidenceLevel,
|
|
118
|
+
evidence: Evidence,
|
|
119
|
+
evidenceFloor: EvidenceFloor,
|
|
120
|
+
claimEvidence: ClaimEvidence,
|
|
121
|
+
claimSummary: ClaimSummary,
|
|
122
|
+
unprovedClaims: UnprovedClaims,
|
|
108
123
|
proofGaps: Gaps,
|
|
109
124
|
proofAdvisories: Advisories,
|
|
110
125
|
proofRepairs: Repairs,
|
|
111
126
|
proofStages: Stages,
|
|
112
127
|
testResolutions: ScenarioTestStage.resolutions
|
|
113
|
-
}
|
|
128
|
+
},
|
|
129
|
+
( Rejections == []
|
|
130
|
+
-> Proof1 = Proof0
|
|
131
|
+
; Proof1 = Proof0.put(certificateRejections, Rejections)
|
|
132
|
+
),
|
|
133
|
+
( BindingRejections == []
|
|
134
|
+
-> Proof = Proof1
|
|
135
|
+
; Proof = Proof1.put(claimBindingRejections, BindingRejections)
|
|
136
|
+
).
|
|
114
137
|
|
|
115
|
-
exempt_requirement_proof(Reason, Proof) :-
|
|
138
|
+
exempt_requirement_proof(Reason, ReqProps, Proof) :-
|
|
139
|
+
requirement_evidence(ReqProps, not_applicable, [], [], EvidenceLevel, Evidence),
|
|
116
140
|
proof_version(Version),
|
|
117
141
|
Proof = _{
|
|
118
142
|
proofVersion: Version,
|
|
119
143
|
proofStatus: not_applicable,
|
|
144
|
+
evidenceLevel: EvidenceLevel,
|
|
145
|
+
evidence: Evidence,
|
|
146
|
+
claimEvidence: [],
|
|
147
|
+
claimSummary: _{applicable: 0, proved: 0, attested: 0, tested: 0, unresolved: 0},
|
|
148
|
+
unprovedClaims: [],
|
|
120
149
|
proofGaps: [],
|
|
121
150
|
proofAdvisories: [],
|
|
122
151
|
proofRepairs: [],
|
|
@@ -443,10 +472,30 @@ inventory_ground_text_mismatches(Inventory, Evidence, Keys) :-
|
|
|
443
472
|
normalize_atom(RawKey, Key),
|
|
444
473
|
inventory_entry_field(Entry, claim_text, RawInventoryText),
|
|
445
474
|
normalize_atom(RawInventoryText, InventoryText),
|
|
446
|
-
InventoryText
|
|
475
|
+
\+ claim_texts_equivalent(InventoryText, Item.claimText)),
|
|
447
476
|
Keys0),
|
|
448
477
|
sort(Keys0, Keys).
|
|
449
478
|
|
|
479
|
+
% implements REQ-kibi-conservative-requirement-proof
|
|
480
|
+
% Inventory and fact claim texts name the same sentence when they differ only
|
|
481
|
+
% in whitespace or trailing punctuation ("…outcomes." vs "…outcomes").
|
|
482
|
+
claim_texts_equivalent(Left, Right) :-
|
|
483
|
+
canonical_claim_text(Left, Canonical),
|
|
484
|
+
canonical_claim_text(Right, Canonical).
|
|
485
|
+
|
|
486
|
+
canonical_claim_text(Text, Canonical) :-
|
|
487
|
+
normalize_space(codes(Codes0), Text),
|
|
488
|
+
reverse(Codes0, Reversed0),
|
|
489
|
+
drop_trailing_claim_punctuation(Reversed0, Reversed),
|
|
490
|
+
reverse(Reversed, Codes),
|
|
491
|
+
atom_codes(Canonical, Codes).
|
|
492
|
+
|
|
493
|
+
drop_trailing_claim_punctuation([Code|Rest], Trimmed) :-
|
|
494
|
+
(code_type(Code, space) ; memberchk(Code, `.,;:!?`)),
|
|
495
|
+
!,
|
|
496
|
+
drop_trailing_claim_punctuation(Rest, Trimmed).
|
|
497
|
+
drop_trailing_claim_punctuation(Codes, Codes).
|
|
498
|
+
|
|
450
499
|
linked_ground_keys(ReqId, Keys) :-
|
|
451
500
|
findall(Key, linked_ground_claim_key(ReqId, Key), Keys0),
|
|
452
501
|
sort(Keys0, Keys).
|
|
@@ -904,7 +953,9 @@ proof_receipt_entries(Props, Receipts, Present) :-
|
|
|
904
953
|
|
|
905
954
|
valid_receipt_shape(TestId, Receipt) :-
|
|
906
955
|
inventory_entry_field(Receipt, version, RawVersion),
|
|
907
|
-
normalize_receipt_atom(RawVersion,
|
|
956
|
+
normalize_receipt_atom(RawVersion, Version),
|
|
957
|
+
receipt_version(Version),
|
|
958
|
+
valid_receipt_version_fields(Version, Receipt),
|
|
908
959
|
inventory_entry_field(Receipt, receipt_id, RawReceiptId),
|
|
909
960
|
normalize_receipt_atom(RawReceiptId, ReceiptId), valid_receipt_id(ReceiptId),
|
|
910
961
|
inventory_entry_field(Receipt, test_id, RawTestId),
|
|
@@ -949,6 +1000,34 @@ valid_receipt_shape(TestId, Receipt) :-
|
|
|
949
1000
|
receipt_timestamp(Receipt, finished_at, FinishedStamp),
|
|
950
1001
|
FinishedStamp >= StartedStamp.
|
|
951
1002
|
|
|
1003
|
+
% kibi.proof-receipt.v2 adds the contract kind and the evidence level the
|
|
1004
|
+
% receipt supports; v1 receipts are still read, as e2e evidence at tested.
|
|
1005
|
+
receipt_version('kibi.proof-receipt.v1').
|
|
1006
|
+
receipt_version('kibi.proof-receipt.v2').
|
|
1007
|
+
|
|
1008
|
+
valid_receipt_version_fields('kibi.proof-receipt.v1', _Receipt).
|
|
1009
|
+
valid_receipt_version_fields('kibi.proof-receipt.v2', Receipt) :-
|
|
1010
|
+
inventory_entry_field(Receipt, kind, RawKind),
|
|
1011
|
+
normalize_receipt_atom(RawKind, Kind),
|
|
1012
|
+
memberchk(Kind, [e2e, example, property, exhaustive, typecheck, arithmetic, smt]),
|
|
1013
|
+
inventory_entry_field(Receipt, evidence_level, RawLevel),
|
|
1014
|
+
normalize_receipt_atom(RawLevel, Level),
|
|
1015
|
+
memberchk(Level, [proved, tested]),
|
|
1016
|
+
( inventory_entry_field(Receipt, certificate_digest, RawDigest)
|
|
1017
|
+
-> normalize_receipt_atom(RawDigest, Digest),
|
|
1018
|
+
valid_sha256(Digest)
|
|
1019
|
+
; true
|
|
1020
|
+
),
|
|
1021
|
+
( Level == proved
|
|
1022
|
+
-> memberchk(Kind, [exhaustive, typecheck, arithmetic, smt]),
|
|
1023
|
+
inventory_entry_field(Receipt, outcome, RawOutcome),
|
|
1024
|
+
normalize_receipt_atom(RawOutcome, passed),
|
|
1025
|
+
inventory_entry_field(Receipt, certificate, _),
|
|
1026
|
+
inventory_entry_field(Receipt, certificate_digest, _),
|
|
1027
|
+
\+ inventory_entry_field(Receipt, certificate_rejection, _)
|
|
1028
|
+
; true
|
|
1029
|
+
).
|
|
1030
|
+
|
|
952
1031
|
receipt_producer_name(Producer, Name) :-
|
|
953
1032
|
inventory_entry_field(Producer, name, RawName),
|
|
954
1033
|
normalize_receipt_atom(RawName, Name).
|
|
@@ -1049,6 +1128,15 @@ receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts) :-
|
|
|
1049
1128
|
receipt_with_binding_hash(Receipt) :-
|
|
1050
1129
|
inventory_entry_field(Receipt, binding_hash, _).
|
|
1051
1130
|
|
|
1131
|
+
% The current binding is one hash, or a list of the hashes a current receipt
|
|
1132
|
+
% may carry (the current binding and the one written before relationship
|
|
1133
|
+
% links left it).
|
|
1134
|
+
receipt_for_binding(Bindings, Receipt) :-
|
|
1135
|
+
is_list(Bindings),
|
|
1136
|
+
!,
|
|
1137
|
+
inventory_entry_field(Receipt, binding_hash, RawBinding),
|
|
1138
|
+
normalize_receipt_atom(RawBinding, Binding),
|
|
1139
|
+
memberchk(Binding, Bindings).
|
|
1052
1140
|
receipt_for_binding(Binding, Receipt) :-
|
|
1053
1141
|
inventory_entry_field(Receipt, binding_hash, RawBinding),
|
|
1054
1142
|
normalize_receipt_atom(RawBinding, Binding).
|
|
@@ -1061,7 +1149,8 @@ receipt_matches_current_binding(Scope, ContractBinding, Receipt) :-
|
|
|
1061
1149
|
receipt_matches_current_contract(none, _Receipt).
|
|
1062
1150
|
receipt_matches_current_contract(hash(ExpectedHash), Receipt) :-
|
|
1063
1151
|
inventory_entry_field(Receipt, version, RawVersion),
|
|
1064
|
-
normalize_receipt_atom(RawVersion,
|
|
1152
|
+
normalize_receipt_atom(RawVersion, Version),
|
|
1153
|
+
receipt_version(Version),
|
|
1065
1154
|
inventory_entry_field(Receipt, contract_hash, RawContractHash),
|
|
1066
1155
|
normalize_receipt_atom(RawContractHash, ExpectedHash).
|
|
1067
1156
|
|
|
@@ -1158,7 +1247,7 @@ receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
|
|
|
1158
1247
|
receipt_string_field(Receipt, started_at, StartedAt),
|
|
1159
1248
|
receipt_string_field(Receipt, finished_at, FinishedAt),
|
|
1160
1249
|
receipt_string_field(Receipt, artifact_digest, ArtifactDigest),
|
|
1161
|
-
|
|
1250
|
+
Evidence0 = _{
|
|
1162
1251
|
testId: TestId,
|
|
1163
1252
|
state: State,
|
|
1164
1253
|
scope: Scope,
|
|
@@ -1170,7 +1259,62 @@ receipt_evidence_dict(TestId, Scope, Receipt, State, AgeSeconds, Evidence) :-
|
|
|
1170
1259
|
finishedAt: FinishedAt,
|
|
1171
1260
|
artifactDigest: ArtifactDigest,
|
|
1172
1261
|
ageSeconds: AgeSeconds
|
|
1173
|
-
}
|
|
1262
|
+
},
|
|
1263
|
+
receipt_kind_evidence(Receipt, State, Evidence0, Evidence).
|
|
1264
|
+
|
|
1265
|
+
% implements REQ-kibi-code-proof-kinds
|
|
1266
|
+
% A v2 receipt adds its contract kind and the evidence level it supports.
|
|
1267
|
+
% proved needs the certificate to re-check now, not only when it was
|
|
1268
|
+
% ingested: its digest must still match and code_certificates.pl must accept
|
|
1269
|
+
% it. The digest is an unsigned hash, so this catches edits and corruption,
|
|
1270
|
+
% not deliberate forgery. When a receipt that claimed proved no longer
|
|
1271
|
+
% re-checks, the evidence says why (certificateRejection).
|
|
1272
|
+
receipt_kind_evidence(Receipt, State, Evidence0, Evidence) :-
|
|
1273
|
+
receipt_string_field(Receipt, version, 'kibi.proof-receipt.v2'),
|
|
1274
|
+
receipt_string_field(Receipt, kind, Kind),
|
|
1275
|
+
!,
|
|
1276
|
+
( State == passed,
|
|
1277
|
+
receipt_string_field(Receipt, evidence_level, proved)
|
|
1278
|
+
-> receipt_certificate_recheck(Receipt, Recheck),
|
|
1279
|
+
( Recheck == ok
|
|
1280
|
+
-> Evidence1 = Evidence0.put(_{kind: Kind, evidenceLevel: proved})
|
|
1281
|
+
; Recheck = rejected(Reason),
|
|
1282
|
+
Evidence1 = Evidence0.put(_{kind: Kind, evidenceLevel: tested,
|
|
1283
|
+
certificateRejection: Reason})
|
|
1284
|
+
)
|
|
1285
|
+
; Evidence1 = Evidence0.put(_{kind: Kind, evidenceLevel: tested})
|
|
1286
|
+
),
|
|
1287
|
+
receipt_claim_fields(Receipt, Evidence1, Evidence).
|
|
1288
|
+
receipt_kind_evidence(_Receipt, _State, Evidence, Evidence).
|
|
1289
|
+
|
|
1290
|
+
% receipt_certificate_recheck(+Receipt, -Result): ok or rejected(Reason).
|
|
1291
|
+
receipt_certificate_recheck(Receipt, Result) :-
|
|
1292
|
+
( inventory_entry_field(Receipt, certificate, Certificate),
|
|
1293
|
+
is_dict(Certificate)
|
|
1294
|
+
-> true
|
|
1295
|
+
; Certificate = none
|
|
1296
|
+
),
|
|
1297
|
+
( Certificate == none
|
|
1298
|
+
-> Result = rejected("the receipt has no certificate")
|
|
1299
|
+
; \+ receipt_string_field(Receipt, certificate_digest, _)
|
|
1300
|
+
-> Result = rejected("the receipt has no certificate_digest")
|
|
1301
|
+
; \+ ( receipt_string_field(Receipt, test_id, TestId),
|
|
1302
|
+
get_dict(test_id, Certificate, CertificateTestId),
|
|
1303
|
+
atom_string(TestId, CertificateTestId) )
|
|
1304
|
+
-> Result = rejected("the certificate is for another test")
|
|
1305
|
+
; \+ ( receipt_string_field(Receipt, kind, Kind),
|
|
1306
|
+
get_dict(kind, Certificate, CertificateKind),
|
|
1307
|
+
atom_string(Kind, CertificateKind) )
|
|
1308
|
+
-> Result = rejected("the certificate kind is not the receipt kind")
|
|
1309
|
+
; receipt_string_field(Receipt, certificate_digest, Digest),
|
|
1310
|
+
code_certificate_digest(Certificate, Recomputed),
|
|
1311
|
+
\+ atom_string(Digest, Recomputed)
|
|
1312
|
+
-> Result = rejected("the certificate no longer matches its certificate_digest")
|
|
1313
|
+
; code_certificate_check(Certificate, Check),
|
|
1314
|
+
Check = rejected(Reason)
|
|
1315
|
+
-> Result = rejected(Reason)
|
|
1316
|
+
; Result = ok
|
|
1317
|
+
).
|
|
1174
1318
|
|
|
1175
1319
|
receipt_string_field(Receipt, Field, Value) :-
|
|
1176
1320
|
inventory_entry_field(Receipt, Field, RawValue),
|
|
@@ -1226,7 +1370,8 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
|
|
|
1226
1370
|
scenario_test(ScenarioId, TestId)),
|
|
1227
1371
|
ScenarioTests0),
|
|
1228
1372
|
sort(ScenarioTests0, ScenarioTests),
|
|
1229
|
-
|
|
1373
|
+
requirement_proof_context(Context),
|
|
1374
|
+
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, [], Context, Stage, ProductionSymbols).
|
|
1230
1375
|
|
|
1231
1376
|
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Stage, ProductionSymbols) :-
|
|
1232
1377
|
findall(SymbolId,
|
|
@@ -1237,12 +1382,16 @@ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence,
|
|
|
1237
1382
|
sort(Implementing0, ImplementingSymbols),
|
|
1238
1383
|
include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
|
|
1239
1384
|
exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
|
|
1240
|
-
include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols,
|
|
1241
|
-
|
|
1385
|
+
include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredProduction),
|
|
1386
|
+
structural_unit_receipts(StructuralSymbols, Context, UnitReceipts),
|
|
1387
|
+
exclude(structural_symbol_covered(PassingE2eTests, UnitReceipts), StructuralSymbols, UncoveredStructural),
|
|
1388
|
+
append(UncoveredProduction, UncoveredStructural, UncoveredSymbols),
|
|
1389
|
+
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests,
|
|
1390
|
+
UncoveredProduction, UncoveredStructural, Status, StatusReason),
|
|
1242
1391
|
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
|
|
1243
1392
|
production_coverage_explanations(
|
|
1244
1393
|
ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
|
|
1245
|
-
PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Explanations),
|
|
1394
|
+
PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context, Explanations),
|
|
1246
1395
|
StageBase = _{
|
|
1247
1396
|
status: Status,
|
|
1248
1397
|
symbols: ProductionSymbols,
|
|
@@ -1267,13 +1416,40 @@ type_shape_symbol_with_structural_contract(SymbolId) :-
|
|
|
1267
1416
|
normalize_atom(RawStatus, Status),
|
|
1268
1417
|
memberchk(Status, [active, passing]).
|
|
1269
1418
|
|
|
1419
|
+
% implements REQ-kibi-conservative-requirement-proof
|
|
1420
|
+
% A type-shape symbol is structurally covered by a unit test only while that
|
|
1421
|
+
% test holds a current passing receipt: an authored status alone is a claim,
|
|
1422
|
+
% not evidence. Receipt states are computed once per distinct unit test.
|
|
1423
|
+
structural_unit_receipts(StructuralSymbols, Context, UnitReceipts) :-
|
|
1424
|
+
findall(TestId,
|
|
1425
|
+
(member(SymbolId, StructuralSymbols),
|
|
1426
|
+
kb_relationship(covered_by, SymbolId, TestId),
|
|
1427
|
+
structural_unit_contract_test(TestId)),
|
|
1428
|
+
TestIds0),
|
|
1429
|
+
sort(TestIds0, TestIds),
|
|
1430
|
+
findall(TestId-State,
|
|
1431
|
+
(member(TestId, TestIds),
|
|
1432
|
+
test_receipt_evidence(Context, [unit], TestId, Evidence),
|
|
1433
|
+
State = Evidence.state),
|
|
1434
|
+
UnitReceipts).
|
|
1435
|
+
|
|
1436
|
+
structural_symbol_covered(PassingE2eTests, _UnitReceipts, SymbolId) :-
|
|
1437
|
+
member(TestId, PassingE2eTests),
|
|
1438
|
+
kb_relationship(covered_by, SymbolId, TestId),
|
|
1439
|
+
!.
|
|
1440
|
+
structural_symbol_covered(_PassingE2eTests, UnitReceipts, SymbolId) :-
|
|
1441
|
+
kb_relationship(covered_by, SymbolId, TestId),
|
|
1442
|
+
memberchk(TestId-passed, UnitReceipts),
|
|
1443
|
+
!.
|
|
1444
|
+
|
|
1270
1445
|
% Stage status plus a typed reason, from a single total clause so status and
|
|
1271
1446
|
% reason can never disagree (a test project's stale-receipts case reported `blocked`
|
|
1272
1447
|
% with no indication that fresh receipts were the unblock). Clause order keeps
|
|
1273
1448
|
% the historical precedence: no production symbols at all is `missing` even
|
|
1274
1449
|
% when E2E evidence is also absent; only then does absent E2E evidence read
|
|
1275
1450
|
% as `blocked`.
|
|
1276
|
-
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests,
|
|
1451
|
+
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests,
|
|
1452
|
+
UncoveredProduction, UncoveredStructural, Status, Reason) :-
|
|
1277
1453
|
( ProductionSymbols == [],
|
|
1278
1454
|
StructuralSymbols == []
|
|
1279
1455
|
-> Status = missing,
|
|
@@ -1281,34 +1457,50 @@ production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, U
|
|
|
1281
1457
|
; PassingE2eTests == []
|
|
1282
1458
|
-> Status = blocked,
|
|
1283
1459
|
Reason = "no passing E2E test evidence is available for the current snapshot; run kibi prove so this stage can evaluate production symbol coverage"
|
|
1284
|
-
;
|
|
1460
|
+
; UncoveredProduction == [],
|
|
1461
|
+
UncoveredStructural == []
|
|
1285
1462
|
-> Status = passed
|
|
1286
|
-
;
|
|
1287
|
-
|
|
1463
|
+
; UncoveredStructural == []
|
|
1464
|
+
-> Status = missing,
|
|
1465
|
+
length(UncoveredProduction, Count),
|
|
1288
1466
|
format(atom(Reason), "~w production symbol(s) lack covered_by links to the passing E2E tests", [Count])
|
|
1467
|
+
; UncoveredProduction == []
|
|
1468
|
+
-> Status = missing,
|
|
1469
|
+
length(UncoveredStructural, Count),
|
|
1470
|
+
format(atom(Reason), "~w type-shape symbol(s) lack a covered_by unit test with a current passing receipt; run kibi prove on those unit tests", [Count])
|
|
1471
|
+
; Status = missing,
|
|
1472
|
+
length(UncoveredProduction, ProductionCount),
|
|
1473
|
+
length(UncoveredStructural, StructuralCount),
|
|
1474
|
+
format(atom(Reason), "~w production symbol(s) lack covered_by links to the passing E2E tests and ~w type-shape symbol(s) lack a covered_by unit test with a current passing receipt", [ProductionCount, StructuralCount])
|
|
1289
1475
|
).
|
|
1290
1476
|
|
|
1291
1477
|
symbol_not_covered_by_tests(Tests, SymbolId) :-
|
|
1292
1478
|
\+ (member(TestId, Tests), kb_relationship(covered_by, SymbolId, TestId)).
|
|
1293
1479
|
|
|
1294
1480
|
production_coverage_explanations(ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
|
|
1295
|
-
PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Explanations) :-
|
|
1481
|
+
PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context, Explanations) :-
|
|
1296
1482
|
maplist(production_symbol_explanation(ReqId, UncoveredSymbols, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context),
|
|
1297
1483
|
ProductionSymbols, ProductionExplanations),
|
|
1298
|
-
maplist(structural_symbol_explanation(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context),
|
|
1484
|
+
maplist(structural_symbol_explanation(ReqId, UncoveredSymbols, PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context),
|
|
1299
1485
|
StructuralSymbols, StructuralExplanations),
|
|
1300
1486
|
append(ProductionExplanations, StructuralExplanations, Explanations).
|
|
1301
1487
|
|
|
1302
|
-
structural_symbol_explanation(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Explanation) :-
|
|
1488
|
+
structural_symbol_explanation(ReqId, UncoveredSymbols, PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context, SymbolId, Explanation) :-
|
|
1303
1489
|
symbol_classification(SymbolId, Classification),
|
|
1304
|
-
symbol_coverage_candidates(structural, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Candidates),
|
|
1305
|
-
|
|
1490
|
+
symbol_coverage_candidates(structural(UnitReceipts), PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Candidates),
|
|
1491
|
+
( memberchk(SymbolId, UncoveredSymbols)
|
|
1492
|
+
-> Status = uncovered,
|
|
1493
|
+
Reason = structural_unit_receipt_not_current
|
|
1494
|
+
; Status = covered,
|
|
1495
|
+
Reason = structural_unit_contract
|
|
1496
|
+
),
|
|
1497
|
+
explanation_reason_text(Reason, ReasonText),
|
|
1306
1498
|
Explanation = _{
|
|
1307
1499
|
symbolId: SymbolId,
|
|
1308
1500
|
requirementId: ReqId,
|
|
1309
1501
|
classification: Classification,
|
|
1310
|
-
status:
|
|
1311
|
-
reason:
|
|
1502
|
+
status: Status,
|
|
1503
|
+
reason: Reason,
|
|
1312
1504
|
reasonText: ReasonText,
|
|
1313
1505
|
coverageCandidates: Candidates
|
|
1314
1506
|
}.
|
|
@@ -1378,9 +1570,20 @@ coverage_candidate_qualification(_Mode, PassingE2eTests, _ScenarioTests, _Receip
|
|
|
1378
1570
|
coverage_candidate_qualification(_Mode, _Passing, _Scenario, _Evidence, _Context, TestId, false, missing_test_entity, []) :-
|
|
1379
1571
|
\+ kb_entity(TestId, test, _),
|
|
1380
1572
|
!.
|
|
1381
|
-
coverage_candidate_qualification(structural, _Passing, _Scenario, _Evidence, _Context, TestId,
|
|
1573
|
+
coverage_candidate_qualification(structural(UnitReceipts), _Passing, _Scenario, _Evidence, _Context, TestId, Qualifies, Primary, Secondaries) :-
|
|
1382
1574
|
structural_unit_contract_test(TestId),
|
|
1383
|
-
|
|
1575
|
+
!,
|
|
1576
|
+
( memberchk(TestId-passed, UnitReceipts)
|
|
1577
|
+
-> Qualifies = true,
|
|
1578
|
+
Primary = structural_unit_contract,
|
|
1579
|
+
Secondaries = []
|
|
1580
|
+
; Qualifies = false,
|
|
1581
|
+
Primary = structural_unit_receipt_not_current,
|
|
1582
|
+
( memberchk(TestId-State, UnitReceipts)
|
|
1583
|
+
-> unit_receipt_secondary(State, Secondaries)
|
|
1584
|
+
; Secondaries = []
|
|
1585
|
+
)
|
|
1586
|
+
).
|
|
1384
1587
|
coverage_candidate_qualification(_Mode, _Passing, ScenarioTests, ReceiptEvidence, Context, TestId, false, Primary, Secondaries) :-
|
|
1385
1588
|
cheap_scope_reject_reason(TestId, ScopeReason),
|
|
1386
1589
|
( \+ memberchk(TestId, ScenarioTests)
|
|
@@ -1394,6 +1597,14 @@ coverage_candidate_qualification(_Mode, _Passing, ScenarioTests, ReceiptEvidence
|
|
|
1394
1597
|
; receipt_reject_for_in_chain(ReceiptEvidence, Context, TestId, Primary, Secondaries)
|
|
1395
1598
|
).
|
|
1396
1599
|
|
|
1600
|
+
unit_receipt_secondary(missing, [missing_proof_receipt]) :- !.
|
|
1601
|
+
unit_receipt_secondary(stale, [stale_proof_receipt]) :- !.
|
|
1602
|
+
unit_receipt_secondary(contract_mismatch, [receipt_contract_mismatch]) :- !.
|
|
1603
|
+
unit_receipt_secondary(failed, [failed_proof_receipt]) :- !.
|
|
1604
|
+
unit_receipt_secondary(invalid, [invalid_proof_receipt]) :- !.
|
|
1605
|
+
unit_receipt_secondary(snapshot_unavailable, [proof_snapshot_unavailable]) :- !.
|
|
1606
|
+
unit_receipt_secondary(_, []).
|
|
1607
|
+
|
|
1397
1608
|
structural_unit_contract_test(TestId) :-
|
|
1398
1609
|
kb_entity(TestId, test, TestProps),
|
|
1399
1610
|
test_scope(TestProps, unit),
|
|
@@ -1482,6 +1693,7 @@ explanation_reason_text_def(covered, "Qualifying covered_by to a passing scenari
|
|
|
1482
1693
|
explanation_reason_text_def(covered_by_missing, "No covered_by relationship to any test").
|
|
1483
1694
|
explanation_reason_text_def(no_qualifying_e2e_coverage, "covered_by relationships exist but none qualify as passing scenario-backed E2E coverage").
|
|
1484
1695
|
explanation_reason_text_def(structural_unit_contract, "type-shape symbol is satisfied by a unit structural contract").
|
|
1696
|
+
explanation_reason_text_def(structural_unit_receipt_not_current, "type-shape symbol needs a covered_by unit test with a current passing receipt; run kibi prove on that unit test").
|
|
1485
1697
|
explanation_reason_text_def(executable_test, "Symbol is executable test code linked with executable_for").
|
|
1486
1698
|
explanation_reason_text_def(stage_blocked_no_passing_e2e, "Production coverage cannot be evaluated because no passing E2E evidence is available for the current snapshot").
|
|
1487
1699
|
explanation_reason_text_def(missing_test_entity, "covered_by target is not a test entity").
|
|
@@ -1674,7 +1886,390 @@ proof_status(Stages, unresolved) :-
|
|
|
1674
1886
|
stage_dict(Stages, Stage),
|
|
1675
1887
|
memberchk(Stage.status, [unresolved, blocked]),
|
|
1676
1888
|
!.
|
|
1677
|
-
proof_status(_,
|
|
1889
|
+
proof_status(_, tested).
|
|
1890
|
+
|
|
1891
|
+
% implements REQ-kibi-code-proof-kinds
|
|
1892
|
+
% Scenario tests whose receipt claimed proved but whose certificate no
|
|
1893
|
+
% longer re-checks, with the reason, so a drop to tested is never silent.
|
|
1894
|
+
certificate_rejections(ScenarioTestStage, Context, Rejections) :-
|
|
1895
|
+
( get_dict(obligations, ScenarioTestStage, Obligations)
|
|
1896
|
+
-> true
|
|
1897
|
+
; Obligations = []
|
|
1898
|
+
),
|
|
1899
|
+
findall(_{test: TestId, reason: Reason},
|
|
1900
|
+
(member(Obligation, Obligations),
|
|
1901
|
+
member(TestId, Obligation.tests),
|
|
1902
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
1903
|
+
get_dict(certificateRejection, Evidence, Reason)),
|
|
1904
|
+
Rejections0),
|
|
1905
|
+
sort(Rejections0, Rejections).
|
|
1906
|
+
|
|
1907
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
1908
|
+
%% receipt_claim_fields(+Receipt, +Evidence0, -Evidence)
|
|
1909
|
+
% A certificate receipt keeps the certificate and the claim bindings Kibi
|
|
1910
|
+
% recorded at ingest, so each claim can be judged against the current KB.
|
|
1911
|
+
receipt_claim_fields(Receipt, Evidence0, Evidence) :-
|
|
1912
|
+
( inventory_entry_field(Receipt, claim_bindings, RawBindings),
|
|
1913
|
+
inventory_entries(RawBindings, Bindings),
|
|
1914
|
+
Bindings \== []
|
|
1915
|
+
-> Evidence1 = Evidence0.put(claimBindings, Bindings)
|
|
1916
|
+
; Evidence1 = Evidence0
|
|
1917
|
+
),
|
|
1918
|
+
( inventory_entry_field(Receipt, certificate, Certificate),
|
|
1919
|
+
is_dict(Certificate)
|
|
1920
|
+
-> Evidence = Evidence1.put(certificate, Certificate)
|
|
1921
|
+
; Evidence = Evidence1
|
|
1922
|
+
).
|
|
1923
|
+
|
|
1924
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
1925
|
+
%% claim_evidence(+ReqId, +Props, +Inventory, +ProofStatus, +ScenarioTestStage,
|
|
1926
|
+
%% +Context, -ClaimEvidence, -ProvedTests, -Rejections)
|
|
1927
|
+
% One entry per semantic-inventory claim with the evidence level that claim
|
|
1928
|
+
% has on its own. A claim is proved only when the requirement is tested end
|
|
1929
|
+
% to end and a fresh certificate whose obligation Kibi derived from the
|
|
1930
|
+
% claim's grounding fact (binding derived, scope code) re-checks. A
|
|
1931
|
+
% certificate the author only attests to the claim leaves it tested.
|
|
1932
|
+
% ProvedTests is non-empty, as TestId-Kind pairs, only when every applicable
|
|
1933
|
+
% claim is proved; the requirement is proved exactly then.
|
|
1934
|
+
claim_evidence(ReqId, Props, Inventory, ProofStatus, ScenarioTestStage, Context,
|
|
1935
|
+
ClaimEvidence, ProvedTests, Rejections) :-
|
|
1936
|
+
( get_dict(tests, ScenarioTestStage, Tests) -> true ; Tests = [] ),
|
|
1937
|
+
( ProofStatus == tested
|
|
1938
|
+
-> findall(TestId-Evidence,
|
|
1939
|
+
(member(TestId, Tests),
|
|
1940
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
1941
|
+
get_dict(state, Evidence, passed),
|
|
1942
|
+
get_dict(claimBindings, Evidence, _)),
|
|
1943
|
+
BoundTests)
|
|
1944
|
+
; BoundTests = []
|
|
1945
|
+
),
|
|
1946
|
+
intent_claim_level(Props, IntentLevel),
|
|
1947
|
+
maplist(claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests), Inventory, Entries0),
|
|
1948
|
+
exclude(==(none), Entries0, ClaimEvidence),
|
|
1949
|
+
findall(R, (member(E, ClaimEvidence), get_dict(rejections, E, Rs), member(R, Rs)), Rejections0),
|
|
1950
|
+
sort(Rejections0, Rejections),
|
|
1951
|
+
include(applicable_claim, ClaimEvidence, Applicable),
|
|
1952
|
+
( ProofStatus == tested,
|
|
1953
|
+
Applicable \== [],
|
|
1954
|
+
forall(member(E, Applicable), claim_level_is(proved, E))
|
|
1955
|
+
-> findall(T-K,
|
|
1956
|
+
(member(E, Applicable), get_dict(bindings, E, Bs), member(B, Bs),
|
|
1957
|
+
get_dict(binding, B, derived), get_dict(test, B, T), get_dict(kind, B, K)),
|
|
1958
|
+
Pairs0),
|
|
1959
|
+
sort(Pairs0, ProvedTests)
|
|
1960
|
+
; ProvedTests = []
|
|
1961
|
+
).
|
|
1962
|
+
|
|
1963
|
+
intent_claim_level(Props, Level) :-
|
|
1964
|
+
( human_intent(Props, _, _) -> Level = asserted ; Level = unverified ).
|
|
1965
|
+
|
|
1966
|
+
applicable_claim(Entry) :- get_dict(applicable, Entry, true).
|
|
1967
|
+
|
|
1968
|
+
claim_level_is(Level, Entry) :- get_dict(level, Entry, Level).
|
|
1969
|
+
|
|
1970
|
+
claim_attested_only(Entry) :-
|
|
1971
|
+
\+ claim_level_is(proved, Entry),
|
|
1972
|
+
get_dict(bindings, Entry, Bindings),
|
|
1973
|
+
member(B, Bindings),
|
|
1974
|
+
get_dict(binding, B, attested),
|
|
1975
|
+
!.
|
|
1976
|
+
|
|
1977
|
+
claim_unresolved(Entry) :- get_dict(blocker, Entry, _).
|
|
1978
|
+
|
|
1979
|
+
claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests, InventoryEntry, Entry) :-
|
|
1980
|
+
( inventory_entry_field(InventoryEntry, claim_key, RawKey),
|
|
1981
|
+
normalize_atom(RawKey, Key)
|
|
1982
|
+
-> inventory_entry_status(InventoryEntry, Status),
|
|
1983
|
+
( inventory_entry_field(InventoryEntry, role, RawRole)
|
|
1984
|
+
-> normalize_atom(RawRole, Role)
|
|
1985
|
+
; Role = ''
|
|
1986
|
+
),
|
|
1987
|
+
Base = _{claimKey: Key, status: Status, role: Role},
|
|
1988
|
+
( Status == nonlogical
|
|
1989
|
+
-> Entry = Base.put(_{applicable: false, level: null, bindings: []})
|
|
1990
|
+
; Status \== modeled
|
|
1991
|
+
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: [],
|
|
1992
|
+
blocker: unresolved_semantic_proposition})
|
|
1993
|
+
; ProofStatus \== tested
|
|
1994
|
+
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: []})
|
|
1995
|
+
; findall(Binding, test_claim_binding(ReqId, Key, BoundTests, Binding), Bindings0),
|
|
1996
|
+
sort(Bindings0, Bindings),
|
|
1997
|
+
include(binding_is(rejected), Bindings, RejectedBindings),
|
|
1998
|
+
maplist(binding_rejection(Key), RejectedBindings, Rejections),
|
|
1999
|
+
( member(B, Bindings), get_dict(binding, B, derived), get_dict(scope, B, code)
|
|
2000
|
+
-> Level = proved
|
|
2001
|
+
; Level = tested
|
|
2002
|
+
),
|
|
2003
|
+
Entry0 = Base.put(_{applicable: true, level: Level, bindings: Bindings}),
|
|
2004
|
+
( Rejections == []
|
|
2005
|
+
-> Entry = Entry0
|
|
2006
|
+
; Entry = Entry0.put(rejections, Rejections)
|
|
2007
|
+
)
|
|
2008
|
+
)
|
|
2009
|
+
; Entry = none
|
|
2010
|
+
).
|
|
2011
|
+
|
|
2012
|
+
binding_is(Expected, Binding) :- get_dict(binding, Binding, Expected).
|
|
2013
|
+
|
|
2014
|
+
binding_rejection(Key, Binding, _{test: Test, claimKey: Key, reason: Reason}) :-
|
|
2015
|
+
get_dict(test, Binding, Test),
|
|
2016
|
+
get_dict(reason, Binding, Reason).
|
|
2017
|
+
|
|
2018
|
+
% The binding one passed receipt records for this claim of this requirement,
|
|
2019
|
+
% re-judged against the current KB.
|
|
2020
|
+
test_claim_binding(ReqId, Key, BoundTests, Binding) :-
|
|
2021
|
+
member(TestId-Evidence, BoundTests),
|
|
2022
|
+
get_dict(claimBindings, Evidence, Recorded0),
|
|
2023
|
+
member(Recorded, Recorded0),
|
|
2024
|
+
inventory_entry_field(Recorded, claim_key, RawKey),
|
|
2025
|
+
normalize_atom(RawKey, Key),
|
|
2026
|
+
inventory_entry_field(Recorded, requirement_id, RawReq),
|
|
2027
|
+
normalize_atom(RawReq, ReqId),
|
|
2028
|
+
( get_dict(kind, Evidence, Kind) -> true ; Kind = e2e ),
|
|
2029
|
+
judge_claim_binding(ReqId, Key, Kind, Evidence, Recorded, Verdict, Scope, Reason),
|
|
2030
|
+
Binding0 = _{test: TestId, kind: Kind, binding: Verdict, scope: Scope},
|
|
2031
|
+
( Reason == ''
|
|
2032
|
+
-> Binding = Binding0
|
|
2033
|
+
; Binding = Binding0.put(reason, Reason)
|
|
2034
|
+
).
|
|
2035
|
+
|
|
2036
|
+
% judge_claim_binding(+ReqId, +Key, +Kind, +Evidence, +Recorded, -Verdict, -Scope, -Reason)
|
|
2037
|
+
judge_claim_binding(ReqId, Key, Kind, Evidence, Recorded, Verdict, Scope, Reason) :-
|
|
2038
|
+
receipt_string_field(Recorded, binding, RecordedVerdict),
|
|
2039
|
+
( receipt_string_field(Recorded, scope, Scope0) -> true ; Scope0 = code ),
|
|
2040
|
+
claim_obligation(ReqId-Key, Kind, Current),
|
|
2041
|
+
( RecordedVerdict == rejected
|
|
2042
|
+
-> Verdict = rejected, Scope = Scope0,
|
|
2043
|
+
( receipt_string_field(Recorded, reason, Reason) -> true ; Reason = 'rejected at ingest' )
|
|
2044
|
+
; Current = unresolved(Reason0)
|
|
2045
|
+
-> Verdict = rejected, Scope = Scope0, atom_string(Reason, Reason0)
|
|
2046
|
+
; Current = incompatible(_, Reason0)
|
|
2047
|
+
-> Verdict = rejected, Scope = Scope0, atom_string(Reason, Reason0)
|
|
2048
|
+
; RecordedVerdict == attested
|
|
2049
|
+
-> ( Current = attested(_)
|
|
2050
|
+
-> Verdict = attested, Scope = Scope0, Reason = ''
|
|
2051
|
+
; Verdict = rejected, Scope = Scope0,
|
|
2052
|
+
Reason = 'the claim now has a derivable obligation; re-prove the test so Kibi can derive it'
|
|
2053
|
+
)
|
|
2054
|
+
; RecordedVerdict == derived,
|
|
2055
|
+
Current = obligation(FactId, Obligation)
|
|
2056
|
+
-> Scope = Scope0,
|
|
2057
|
+
( \+ ( receipt_string_field(Recorded, fact_id, RecordedFact), RecordedFact == FactId )
|
|
2058
|
+
-> Verdict = rejected,
|
|
2059
|
+
format(atom(Reason), "~w is now grounded by ~w, not the fact the receipt was ingested against", [Key, FactId])
|
|
2060
|
+
; \+ ( inventory_entry_field(Recorded, obligation, RecordedObligation),
|
|
2061
|
+
obligation_equal(RecordedObligation, Obligation) )
|
|
2062
|
+
-> Verdict = rejected,
|
|
2063
|
+
format(atom(Reason), "~w changed since the receipt was ingested", [FactId])
|
|
2064
|
+
; \+ get_dict(evidenceLevel, Evidence, proved)
|
|
2065
|
+
-> Verdict = rejected,
|
|
2066
|
+
Reason = 'the certificate does not re-check'
|
|
2067
|
+
; certificate_obligation_problem(Kind, Evidence, Obligation, Problem)
|
|
2068
|
+
-> Verdict = rejected, Reason = Problem
|
|
2069
|
+
; Verdict = derived, Reason = ''
|
|
2070
|
+
)
|
|
2071
|
+
; Verdict = rejected, Scope = Scope0,
|
|
2072
|
+
Reason = 'the claim has no derivable obligation; re-prove the test'
|
|
2073
|
+
).
|
|
2074
|
+
|
|
2075
|
+
% The part of a derived binding Kibi can re-check from the stored
|
|
2076
|
+
% certificate alone (code-dependent parts were checked at ingest and stale
|
|
2077
|
+
% with the receipt).
|
|
2078
|
+
certificate_obligation_problem(exhaustive, Evidence, Obligation, Problem) :-
|
|
2079
|
+
get_dict(certificate, Evidence, Certificate),
|
|
2080
|
+
get_dict(domain, Certificate, Domain),
|
|
2081
|
+
get_dict(members, Domain, Raw),
|
|
2082
|
+
maplist([M, S]>>(atom(M) -> atom_string(M, S) ; S = M), Raw, Strings),
|
|
2083
|
+
sort(Strings, Members),
|
|
2084
|
+
\+ obligation_equal(Obligation, _{kind: "set_equals", members: Members}),
|
|
2085
|
+
Problem = 'the certificate domain is not the fact\'s set'.
|
|
2086
|
+
certificate_obligation_problem(arithmetic, Evidence, Obligation, Problem) :-
|
|
2087
|
+
get_dict(certificate, Evidence, Certificate),
|
|
2088
|
+
( get_dict(claim, Certificate, Claim),
|
|
2089
|
+
get_dict(operator, Claim, Op),
|
|
2090
|
+
get_dict(value, Claim, Value),
|
|
2091
|
+
get_dict(variable, Certificate, Variable),
|
|
2092
|
+
get_dict(value_type, Certificate, Type),
|
|
2093
|
+
obligation_equal(Obligation,
|
|
2094
|
+
_{kind: "numeric_constraint", variable: Variable, value_type: Type,
|
|
2095
|
+
operator: Op, value: Value})
|
|
2096
|
+
-> fail
|
|
2097
|
+
; Problem = 'the certificate\'s claim is not the fact\'s constraint'
|
|
2098
|
+
).
|
|
2099
|
+
|
|
2100
|
+
%% claim_summary(+ClaimEvidence, -Summary, -UnprovedClaims)
|
|
2101
|
+
claim_summary(ClaimEvidence, Summary, Unproved) :-
|
|
2102
|
+
include(applicable_claim, ClaimEvidence, Applicable),
|
|
2103
|
+
length(Applicable, ApplicableCount),
|
|
2104
|
+
count_claims(Applicable, claim_level_is(proved), Proved),
|
|
2105
|
+
count_claims(Applicable, claim_attested_only, Attested),
|
|
2106
|
+
count_claims(Applicable, claim_level_is(tested), Tested),
|
|
2107
|
+
count_claims(Applicable, claim_unresolved, Unresolved),
|
|
2108
|
+
Summary = _{applicable: ApplicableCount, proved: Proved, attested: Attested,
|
|
2109
|
+
tested: Tested, unresolved: Unresolved},
|
|
2110
|
+
findall(Key, (member(E, Applicable), \+ claim_level_is(proved, E), get_dict(claimKey, E, Key)), Unproved).
|
|
2111
|
+
|
|
2112
|
+
count_claims(Entries, Goal, Count) :-
|
|
2113
|
+
include(Goal, Entries, Matching),
|
|
2114
|
+
length(Matching, Count).
|
|
2115
|
+
|
|
2116
|
+
% implements REQ-kibi-evidence-levels, REQ-kibi-code-proof-kinds
|
|
2117
|
+
%% requirement_evidence(+Props, +ProofStatus, +PassingE2eTests, +ProvedTests, -Level, -Evidence)
|
|
2118
|
+
% How strong the evidence behind a requirement is, never stronger than what
|
|
2119
|
+
% backs it. Levels, strongest first: proved (the requirement is tested and
|
|
2120
|
+
% every applicable claim is proved by a fresh certificate bound to it with a
|
|
2121
|
+
% derived obligation: ProvedTests is non-empty), tested (passing E2E
|
|
2122
|
+
% receipts cover every linked scenario: proofStatus tested), asserted (a
|
|
2123
|
+
% person stands behind the statement but nothing current checks it),
|
|
2124
|
+
% unverified (an agent wrote it and no person approved it). Evidence lists
|
|
2125
|
+
% what earned the level and anything weaker that also holds, each entry
|
|
2126
|
+
% naming its kind and scope.
|
|
2127
|
+
requirement_evidence(Props, ProofStatus, PassingE2eTests, ProvedTests, Level, Evidence) :-
|
|
2128
|
+
findall(Entry, evidence_entry(Props, ProofStatus, PassingE2eTests, ProvedTests, Entry), Evidence),
|
|
2129
|
+
evidence_level(Evidence, Level).
|
|
2130
|
+
|
|
2131
|
+
evidence_entry(_Props, tested, _PassingE2eTests, ProvedTests, _{kind: certificate, scope: code, level: proved, tests: Tests, kinds: Kinds}) :-
|
|
2132
|
+
ProvedTests \= [],
|
|
2133
|
+
pairs_keys_values(ProvedTests, Tests0, Kinds0),
|
|
2134
|
+
sort(Tests0, Tests),
|
|
2135
|
+
sort(Kinds0, Kinds).
|
|
2136
|
+
evidence_entry(_Props, tested, PassingE2eTests, _ProvedTests, _{kind: e2e, scope: code, level: tested, tests: PassingE2eTests}).
|
|
2137
|
+
evidence_entry(Props, _ProofStatus, _PassingE2eTests, _ProvedTests, Entry) :-
|
|
2138
|
+
human_intent(Props, Basis, Ref),
|
|
2139
|
+
( Ref == ''
|
|
2140
|
+
-> Entry = _{kind: human_intent, scope: intent, level: asserted, basis: Basis}
|
|
2141
|
+
; Entry = _{kind: human_intent, scope: intent, level: asserted, basis: Basis, ref: Ref}
|
|
2142
|
+
).
|
|
2143
|
+
|
|
2144
|
+
evidence_level(Evidence, Level) :-
|
|
2145
|
+
( member(Level0, [proved, tested, asserted]),
|
|
2146
|
+
member(Entry, Evidence),
|
|
2147
|
+
get_dict(level, Entry, Level0)
|
|
2148
|
+
-> Level = Level0
|
|
2149
|
+
; Level = unverified
|
|
2150
|
+
).
|
|
2151
|
+
|
|
2152
|
+
% implements REQ-kibi-evidence-floors
|
|
2153
|
+
%% evidence_floor(+Props, +Level, +Gaps, +Stages, -Floor)
|
|
2154
|
+
% The weakest evidence level the requirement accepts and whether its computed
|
|
2155
|
+
% level reaches it. The floor is the requirement's required_evidence, or,
|
|
2156
|
+
% when absent, tested for a firm requirement and unverified (no floor)
|
|
2157
|
+
% otherwise. Status is met, below, or pending_reproof: the floor is tested
|
|
2158
|
+
% and every gap only says recorded receipts are older than the code, so
|
|
2159
|
+
% re-running kibi prove reaches it without any KB change.
|
|
2160
|
+
evidence_floor(Props, Level, Gaps, Stages, _{required: Required, source: Source, status: Status}) :-
|
|
2161
|
+
required_evidence(Props, Required, Source),
|
|
2162
|
+
evidence_rank(Level, Have),
|
|
2163
|
+
evidence_rank(Required, Need),
|
|
2164
|
+
( Have >= Need
|
|
2165
|
+
-> Status = met
|
|
2166
|
+
; Required == tested,
|
|
2167
|
+
freshness_only_gaps(Gaps, Stages)
|
|
2168
|
+
-> Status = pending_reproof
|
|
2169
|
+
; Status = below
|
|
2170
|
+
).
|
|
2171
|
+
|
|
2172
|
+
required_evidence(Props, Required, explicit) :-
|
|
2173
|
+
memberchk(required_evidence=Raw, Props),
|
|
2174
|
+
normalize_atom(Raw, Required),
|
|
2175
|
+
evidence_rank(Required, _),
|
|
2176
|
+
!.
|
|
2177
|
+
required_evidence(Props, tested, firmness) :-
|
|
2178
|
+
effective_firmness(Props, firm),
|
|
2179
|
+
!.
|
|
2180
|
+
required_evidence(_Props, unverified, firmness).
|
|
2181
|
+
|
|
2182
|
+
evidence_rank(unverified, 0).
|
|
2183
|
+
evidence_rank(asserted, 1).
|
|
2184
|
+
evidence_rank(tested, 2).
|
|
2185
|
+
evidence_rank(proved, 3).
|
|
2186
|
+
|
|
2187
|
+
% Gaps that only say the evidence is stale: kibi prove clears them. Stale
|
|
2188
|
+
% receipts also leave every production symbol uncovered; that gap is
|
|
2189
|
+
% freshness-only when each uncovered symbol has a coverage candidate that
|
|
2190
|
+
% qualifies or is rejected only for stale evidence.
|
|
2191
|
+
freshness_gap(stale_proof_receipt).
|
|
2192
|
+
freshness_gap(proof_contract_mismatch).
|
|
2193
|
+
|
|
2194
|
+
freshness_only_gaps(Gaps, Stages) :-
|
|
2195
|
+
include(freshness_gap, Gaps, [_|_]),
|
|
2196
|
+
exclude(freshness_gap, Gaps, Rest),
|
|
2197
|
+
( Rest == []
|
|
2198
|
+
-> true
|
|
2199
|
+
; Rest == [missing_production_symbol_coverage],
|
|
2200
|
+
production_coverage_freshness_only(Stages)
|
|
2201
|
+
).
|
|
2202
|
+
|
|
2203
|
+
production_coverage_freshness_only(Stages) :-
|
|
2204
|
+
Stage = Stages.productionSymbols,
|
|
2205
|
+
forall(member(SymbolId, Stage.uncoveredSymbols),
|
|
2206
|
+
( member(Explanation, Stage.explanations),
|
|
2207
|
+
Explanation.symbolId == SymbolId,
|
|
2208
|
+
member(Candidate, Explanation.coverageCandidates),
|
|
2209
|
+
freshness_only_candidate(Candidate)
|
|
2210
|
+
)).
|
|
2211
|
+
|
|
2212
|
+
freshness_only_candidate(Candidate) :-
|
|
2213
|
+
Candidate.qualifies == true,
|
|
2214
|
+
!.
|
|
2215
|
+
freshness_only_candidate(Candidate) :-
|
|
2216
|
+
Candidate.scope == unit,
|
|
2217
|
+
Candidate.reason == structural_unit_receipt_not_current,
|
|
2218
|
+
Candidate.secondaryReasons = [_|_],
|
|
2219
|
+
forall(member(Reason, Candidate.secondaryReasons),
|
|
2220
|
+
memberchk(Reason, [stale_proof_receipt, receipt_snapshot_mismatch, receipt_contract_mismatch])),
|
|
2221
|
+
!.
|
|
2222
|
+
freshness_only_candidate(Candidate) :-
|
|
2223
|
+
Candidate.scope == end_to_end,
|
|
2224
|
+
forall(member(Reason, [Candidate.reason|Candidate.secondaryReasons]),
|
|
2225
|
+
memberchk(Reason, [stale_proof_receipt, receipt_snapshot_mismatch, receipt_contract_mismatch])).
|
|
2226
|
+
|
|
2227
|
+
%% human_intent(+Props, -Basis, -Ref)
|
|
2228
|
+
% A person stands behind the requirement: someone approved it
|
|
2229
|
+
% (origin.approved_by, the field agent-requirement-unapproved reads), or it
|
|
2230
|
+
% reads as firm (firmness: firm, or no firmness and an origin other than
|
|
2231
|
+
% agent, as for decision firmness). The top-level approved_by approves an
|
|
2232
|
+
% exception; an agent can write it, so on its own it approves nothing here.
|
|
2233
|
+
human_intent(Props, approved, Ref) :-
|
|
2234
|
+
origin_field(Props, approved_by, _),
|
|
2235
|
+
!,
|
|
2236
|
+
intent_ref(Props, Ref).
|
|
2237
|
+
human_intent(Props, firm, Ref) :-
|
|
2238
|
+
effective_firmness(Props, firm),
|
|
2239
|
+
!,
|
|
2240
|
+
intent_ref(Props, Ref).
|
|
2241
|
+
|
|
2242
|
+
effective_firmness(Props, Firmness) :-
|
|
2243
|
+
( memberchk(firmness=Raw, Props),
|
|
2244
|
+
normalize_atom(Raw, Explicit),
|
|
2245
|
+
memberchk(Explicit, [firm, default, exploratory])
|
|
2246
|
+
-> Firmness = Explicit
|
|
2247
|
+
; origin_field(Props, kind, agent)
|
|
2248
|
+
-> Firmness = default
|
|
2249
|
+
; Firmness = firm
|
|
2250
|
+
).
|
|
2251
|
+
|
|
2252
|
+
intent_ref(Props, Ref) :-
|
|
2253
|
+
( origin_field(Props, ref, Ref0)
|
|
2254
|
+
-> Ref = Ref0
|
|
2255
|
+
; prop_text(Props, approval_ref, Ref0)
|
|
2256
|
+
-> Ref = Ref0
|
|
2257
|
+
; Ref = ''
|
|
2258
|
+
).
|
|
2259
|
+
|
|
2260
|
+
prop_text(Props, Key, Text) :-
|
|
2261
|
+
Prop =.. [=, Key, Raw],
|
|
2262
|
+
memberchk(Prop, Props),
|
|
2263
|
+
normalize_atom(Raw, Text),
|
|
2264
|
+
Text \== ''.
|
|
2265
|
+
|
|
2266
|
+
origin_field(Props, Key, Value) :-
|
|
2267
|
+
memberchk(origin=Raw, Props),
|
|
2268
|
+
normalize_atom(Raw, Json),
|
|
2269
|
+
catch(atom_json_dict(Json, Origin, [value_string_as(atom)]), _, fail),
|
|
2270
|
+
get_dict(Key, Origin, Value0),
|
|
2271
|
+
normalize_atom(Value0, Value),
|
|
2272
|
+
Value \== ''.
|
|
1678
2273
|
|
|
1679
2274
|
stage_dict(Stages, Stage) :-
|
|
1680
2275
|
dict_pairs(Stages, _, Pairs),
|
|
@@ -1690,7 +2285,7 @@ proof_advisories(Stages, Advisories) :-
|
|
|
1690
2285
|
% Every linked scenario is a mandatory proof obligation. Receipt-completeness
|
|
1691
2286
|
% failures therefore remain blocking even when another scenario has a passing
|
|
1692
2287
|
% receipt; unit/integration-only evidence stays nonblocking in the E2E lane.
|
|
1693
|
-
% Invariant: proofStatus ==
|
|
2288
|
+
% Invariant: proofStatus == tested implies proofGaps == [].
|
|
1694
2289
|
proof_issues(Stages, Gaps, Advisories) :-
|
|
1695
2290
|
findall(Gap,
|
|
1696
2291
|
(gap_definition(Gap, _, _, _),
|
|
@@ -1769,7 +2364,7 @@ gap_definition(proof_contract_mismatch, 57, passing_e2e, "Run kibi prove so the
|
|
|
1769
2364
|
gap_definition(proof_snapshot_unavailable, 58, passing_e2e, "Run coverage through a CLI or MCP runtime that exposes the deterministic workspace snapshot.").
|
|
1770
2365
|
gap_definition(missing_executable_test_symbol, 60, executable_symbols, "Link executable test code to every qualifying E2E test with executable_for.").
|
|
1771
2366
|
gap_definition(missing_production_symbol, 70, production_symbols, "Link at least one production symbol to the requirement with implements.").
|
|
1772
|
-
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
|
|
2367
|
+
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 instead need a covered_by unit test with a current passing receipt.").
|
|
1773
2368
|
gap_definition(missing_symbol_coordinates, 80, source_coordinates, "Refresh and persist exact coordinates for every proof-bearing symbol.").
|
|
1774
2369
|
gap_definition(missing_requirement_source, 81, source_coordinates, "Bind the requirement to its current source document.").
|
|
1775
2370
|
|