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