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/discovery.pl
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
coverage_report_json/10,
|
|
8
8
|
coverage_report_json/11,
|
|
9
9
|
coverage_report_json/12,
|
|
10
|
+
coverage_report_json/13,
|
|
10
11
|
coverage_evidence_json/5,
|
|
12
|
+
evidence_floor_json/6,
|
|
11
13
|
requirement_proof_json/5,
|
|
12
14
|
requirement_proof_json/7,
|
|
13
15
|
symbol_proof_json/7,
|
|
16
|
+
test_receipt_states_json/6,
|
|
14
17
|
graph_expand_json/8,
|
|
15
18
|
search_answer_verdicts_json/2,
|
|
16
19
|
ask_predicate_json/9,
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
:- use_module(library(pairs)).
|
|
30
33
|
:- use_module('kb.pl').
|
|
31
34
|
:- use_module(library(semweb/rdf_db), [rdf_generation/1]).
|
|
32
|
-
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4]).
|
|
35
|
+
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4, test_receipt_evidence/4]).
|
|
33
36
|
:- use_module('checks.pl', [scenario_feasibility_outcome/2, infeasible_scenario/5]).
|
|
34
37
|
:- use_module('logic_ir.pl', [logic_rule_conflict/3, logic_rule_conflict/4]).
|
|
35
38
|
:- use_module('status.pl', [status_meta_dict/1, status_scope_dict/1]).
|
|
@@ -78,6 +81,21 @@ requirement_proof_json(ReqId, BindingMode, TestBindings, VerificationSnapshot, C
|
|
|
78
81
|
put_dict(id, Proof, ReqId, ProofWithId),
|
|
79
82
|
dict_json_string(ProofWithId, JsonString).
|
|
80
83
|
|
|
84
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
85
|
+
% The receipt state of every proof-bearing test under the given binding, as
|
|
86
|
+
% `kibi prove --stale` selects them: passed means a current passing receipt.
|
|
87
|
+
test_receipt_states_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
88
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
89
|
+
findall(TestId-State,
|
|
90
|
+
(kb_entity(TestId, test, Props),
|
|
91
|
+
memberchk(proof_contract=_, Props),
|
|
92
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
93
|
+
State = Evidence.state),
|
|
94
|
+
Pairs0),
|
|
95
|
+
sort(Pairs0, Pairs),
|
|
96
|
+
dict_pairs(States, _, Pairs),
|
|
97
|
+
dict_json_string(_{states: States}, JsonString).
|
|
98
|
+
|
|
81
99
|
symbol_proof_json(SymbolId, BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
82
100
|
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
83
101
|
kb_entity(SymbolId, symbol, _),
|
|
@@ -344,6 +362,77 @@ coverage_evidence_json(Tags, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Jso
|
|
|
344
362
|
Response = _{rows: Evidences},
|
|
345
363
|
dict_json_string(Response, JsonString).
|
|
346
364
|
|
|
365
|
+
% implements REQ-kibi-evidence-floors
|
|
366
|
+
% Every current requirement whose evidence level is below its floor, for the
|
|
367
|
+
% evidence-floor check rule: the rows that fail (status below) or that only
|
|
368
|
+
% need kibi prove re-run (status pending_reproof), plus how many current
|
|
369
|
+
% requirements were evaluated. Requirements outside proof scope carry no
|
|
370
|
+
% floor.
|
|
371
|
+
evidence_floor_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
372
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
373
|
+
findall(Id-Proof,
|
|
374
|
+
( kb_entity(Id, req, Props),
|
|
375
|
+
requirement_proof(Id, Props, Context, Proof),
|
|
376
|
+
get_dict(evidenceFloor, Proof, _)
|
|
377
|
+
),
|
|
378
|
+
Evaluated),
|
|
379
|
+
findall(Row,
|
|
380
|
+
( member(Id-Proof, Evaluated),
|
|
381
|
+
Floor = Proof.evidenceFloor,
|
|
382
|
+
Floor.status \== met,
|
|
383
|
+
Row = _{
|
|
384
|
+
id: Id,
|
|
385
|
+
evidenceLevel: Proof.evidenceLevel,
|
|
386
|
+
requiredEvidence: Floor.required,
|
|
387
|
+
requiredEvidenceSource: Floor.source,
|
|
388
|
+
status: Floor.status,
|
|
389
|
+
proofStatus: Proof.proofStatus,
|
|
390
|
+
proofGaps: Proof.proofGaps,
|
|
391
|
+
gapDetails: Details
|
|
392
|
+
},
|
|
393
|
+
evidence_floor_gap_details(Proof, Details)
|
|
394
|
+
),
|
|
395
|
+
Rows0),
|
|
396
|
+
sort(id, @<, Rows0, Rows),
|
|
397
|
+
length(Evaluated, Count),
|
|
398
|
+
dict_json_string(_{evaluated: Count, rows: Rows}, JsonString).
|
|
399
|
+
|
|
400
|
+
% The entities behind an evidence-floor row's gaps, so a person sees what to
|
|
401
|
+
% fix without one kibi proof explain per requirement: the tests whose
|
|
402
|
+
% receipts are missing, stale, failed or invalid, the production symbols no
|
|
403
|
+
% qualifying test covers and the symbols without source coordinates. Empty
|
|
404
|
+
% lists are left out.
|
|
405
|
+
evidence_floor_gap_details(Proof, Details) :-
|
|
406
|
+
( get_dict(proofStages, Proof, Stages)
|
|
407
|
+
-> true
|
|
408
|
+
; Stages = _{}
|
|
409
|
+
),
|
|
410
|
+
findall(Key-Ids,
|
|
411
|
+
( evidence_floor_gap_field(Key, StageKey, Field),
|
|
412
|
+
get_dict(StageKey, Stages, Stage),
|
|
413
|
+
get_dict(Field, Stage, Ids),
|
|
414
|
+
Ids \== []
|
|
415
|
+
),
|
|
416
|
+
Pairs0),
|
|
417
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
418
|
+
% Under a proved floor, the claims no certificate binding derives.
|
|
419
|
+
( get_dict(evidenceFloor, Proof, Floor),
|
|
420
|
+
get_dict(required, Floor, proved),
|
|
421
|
+
get_dict(unprovedClaims, Proof, Unproved),
|
|
422
|
+
Unproved \== []
|
|
423
|
+
-> Pairs = [unprovedClaims-Unproved|Pairs0]
|
|
424
|
+
; Pairs = Pairs0
|
|
425
|
+
),
|
|
426
|
+
dict_pairs(Details, _, Pairs).
|
|
427
|
+
|
|
428
|
+
evidence_floor_gap_field(failedReceiptTests, passingE2e, failedReceiptTests).
|
|
429
|
+
evidence_floor_gap_field(missingReceiptTests, passingE2e, missingReceiptTests).
|
|
430
|
+
evidence_floor_gap_field(staleReceiptTests, passingE2e, staleReceiptTests).
|
|
431
|
+
evidence_floor_gap_field(invalidReceiptTests, passingE2e, invalidReceiptTests).
|
|
432
|
+
evidence_floor_gap_field(contractMismatchReceiptTests, passingE2e, contractMismatchReceiptTests).
|
|
433
|
+
evidence_floor_gap_field(uncoveredSymbols, productionSymbols, uncoveredSymbols).
|
|
434
|
+
evidence_floor_gap_field(missingCoordinateSymbols, sourceCoordinates, missingSymbols).
|
|
435
|
+
|
|
347
436
|
coverage_evidence_row(Id, Proof, Evidence) :-
|
|
348
437
|
( get_dict(proofStatus, Proof, ProofStatus)
|
|
349
438
|
-> true
|
|
@@ -398,7 +487,7 @@ coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, Includ
|
|
|
398
487
|
|
|
399
488
|
% implements REQ-kibi-conservative-requirement-proof
|
|
400
489
|
% Proof-status filtered report. The status filter selects requirement rows by
|
|
401
|
-
% proofStatus (
|
|
490
|
+
% proofStatus (tested, missing, unresolved, not_applicable) and replaces the
|
|
402
491
|
% default include-passing filtering, so callers can enumerate exactly one
|
|
403
492
|
% slice — e.g. every not_applicable row together with its typed applicability
|
|
404
493
|
% reason — without diffing full exports by hand. The summary is always
|
|
@@ -414,6 +503,28 @@ coverage_report_json(By, Tags, IncludePassing, StatusFilter, IncludeTransitive,
|
|
|
414
503
|
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
415
504
|
dict_json_string(Response, JsonString).
|
|
416
505
|
|
|
506
|
+
% Proof-status filtered report with per-contract receipt binding: the same
|
|
507
|
+
% rows and whole-KB summary as the unfiltered report under that binding mode,
|
|
508
|
+
% so a status filter never judges receipts more strictly than kb_coverage,
|
|
509
|
+
% kibi proof explain or the evidence-floor rule do.
|
|
510
|
+
coverage_report_json(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
511
|
+
memo_sorted_coverage(
|
|
512
|
+
status_binding(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
|
|
513
|
+
status_binding_coverage_rows(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
|
|
514
|
+
SortedRows,
|
|
515
|
+
Summary),
|
|
516
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
517
|
+
status_meta_dict(Meta),
|
|
518
|
+
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
519
|
+
dict_json_string(Response, JsonString).
|
|
520
|
+
|
|
521
|
+
status_binding_coverage_rows(req, Tags, _IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows, Summary) :-
|
|
522
|
+
!,
|
|
523
|
+
coverage_rows(req, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows0, Summary),
|
|
524
|
+
include(req_row_proof_status_in(StatusFilter), Rows0, MatchedRows).
|
|
525
|
+
status_binding_coverage_rows(By, Tags, IncludePassing, StatusFilter, _BindingMode, _TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
526
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
527
|
+
|
|
417
528
|
binding_coverage_rows(req, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
418
529
|
!,
|
|
419
530
|
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows, Summary).
|
|
@@ -1118,6 +1229,10 @@ symbol_coverage_row(Tags, Row) :-
|
|
|
1118
1229
|
coverageStatus: CoverageStatus
|
|
1119
1230
|
}.
|
|
1120
1231
|
|
|
1232
|
+
% Link coverage (evaluated, fullyCovered, uncovered, notApplicable and the
|
|
1233
|
+
% missing* counts) judges only priority: must requirements; linkCoverageScope
|
|
1234
|
+
% says so, so a project without must requirements does not read as having
|
|
1235
|
+
% evaluated nothing next to its proof counts.
|
|
1121
1236
|
coverage_summary(Rows, Summary) :-
|
|
1122
1237
|
length(Rows, Total),
|
|
1123
1238
|
include(req_row_evaluated, Rows, EvaluatedRows),
|
|
@@ -1127,7 +1242,7 @@ coverage_summary(Rows, Summary) :-
|
|
|
1127
1242
|
include(req_row_missing_scenario, Rows, MissingScenarioRows),
|
|
1128
1243
|
include(req_row_missing_test, Rows, MissingTestRows),
|
|
1129
1244
|
include(req_row_missing_both, Rows, MissingBothRows),
|
|
1130
|
-
include(
|
|
1245
|
+
include(req_row_proof_tested, Rows, ProofTestedRows),
|
|
1131
1246
|
include(req_row_proof_unresolved, Rows, ProofUnresolvedRows),
|
|
1132
1247
|
include(req_row_proof_missing, Rows, ProofMissingRows),
|
|
1133
1248
|
include(req_row_proof_not_applicable, Rows, ProofNotApplicableRows),
|
|
@@ -1138,7 +1253,10 @@ coverage_summary(Rows, Summary) :-
|
|
|
1138
1253
|
length(MissingScenarioRows, MissingScenario),
|
|
1139
1254
|
length(MissingTestRows, MissingTest),
|
|
1140
1255
|
length(MissingBothRows, MissingScenarioAndTest),
|
|
1141
|
-
length(
|
|
1256
|
+
length(ProofTestedRows, ProofTested),
|
|
1257
|
+
evidence_level_counts(Rows, EvidenceLevels),
|
|
1258
|
+
claim_level_counts(Rows, ProofClaims),
|
|
1259
|
+
evidence_floor_counts(Rows, EvidenceFloors),
|
|
1142
1260
|
length(ProofUnresolvedRows, ProofUnresolved),
|
|
1143
1261
|
length(ProofMissingRows, ProofMissing),
|
|
1144
1262
|
length(ProofNotApplicableRows, ProofNotApplicable),
|
|
@@ -1151,12 +1269,61 @@ coverage_summary(Rows, Summary) :-
|
|
|
1151
1269
|
missingScenario: MissingScenario,
|
|
1152
1270
|
missingTest: MissingTest,
|
|
1153
1271
|
missingScenarioAndTest: MissingScenarioAndTest,
|
|
1154
|
-
|
|
1272
|
+
proofTested: ProofTested,
|
|
1155
1273
|
proofUnresolved: ProofUnresolved,
|
|
1156
1274
|
proofMissing: ProofMissing,
|
|
1157
|
-
proofNotApplicable: ProofNotApplicable
|
|
1275
|
+
proofNotApplicable: ProofNotApplicable,
|
|
1276
|
+
evidenceLevels: EvidenceLevels,
|
|
1277
|
+
proofClaims: ProofClaims,
|
|
1278
|
+
evidenceFloors: EvidenceFloors,
|
|
1279
|
+
linkCoverageScope: priority_must
|
|
1158
1280
|
}.
|
|
1159
1281
|
|
|
1282
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
1283
|
+
% The applicable requirement claims across Rows, by the level their evidence
|
|
1284
|
+
% reaches: a claim is proved only through a derived certificate binding.
|
|
1285
|
+
claim_level_counts(Rows, _{applicable: Applicable, proved: Proved, attested: Attested,
|
|
1286
|
+
tested: Tested, unresolved: Unresolved}) :-
|
|
1287
|
+
claim_count_sum(Rows, applicable, Applicable),
|
|
1288
|
+
claim_count_sum(Rows, proved, Proved),
|
|
1289
|
+
claim_count_sum(Rows, attested, Attested),
|
|
1290
|
+
claim_count_sum(Rows, tested, Tested),
|
|
1291
|
+
claim_count_sum(Rows, unresolved, Unresolved).
|
|
1292
|
+
|
|
1293
|
+
claim_count_sum(Rows, Key, Sum) :-
|
|
1294
|
+
aggregate_all(sum(N),
|
|
1295
|
+
( member(Row, Rows),
|
|
1296
|
+
get_dict(claimSummary, Row, Summary),
|
|
1297
|
+
get_dict(Key, Summary, N),
|
|
1298
|
+
number(N)
|
|
1299
|
+
),
|
|
1300
|
+
Sum).
|
|
1301
|
+
|
|
1302
|
+
% implements REQ-kibi-evidence-floors
|
|
1303
|
+
% Current requirements with a floor (required_evidence, or tested for a firm
|
|
1304
|
+
% requirement), by whether their evidence level reaches it. A floor of
|
|
1305
|
+
% unverified accepts anything and is not counted.
|
|
1306
|
+
evidence_floor_counts(Rows, _{met: Met, below: Below, pendingReproof: Pending}) :-
|
|
1307
|
+
evidence_floor_status_count(Rows, met, Met),
|
|
1308
|
+
evidence_floor_status_count(Rows, below, Below),
|
|
1309
|
+
evidence_floor_status_count(Rows, pending_reproof, Pending).
|
|
1310
|
+
|
|
1311
|
+
evidence_floor_status_count(Rows, Status, Count) :-
|
|
1312
|
+
aggregate_all(count,
|
|
1313
|
+
( member(Row, Rows),
|
|
1314
|
+
get_dict(evidenceFloor, Row, Floor),
|
|
1315
|
+
Floor.get(required) \== unverified,
|
|
1316
|
+
Floor.get(status) == Status
|
|
1317
|
+
),
|
|
1318
|
+
Count).
|
|
1319
|
+
|
|
1320
|
+
% implements REQ-kibi-evidence-levels
|
|
1321
|
+
evidence_level_counts(Rows, _{proved: Proved, tested: Tested, asserted: Asserted, unverified: Unverified}) :-
|
|
1322
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == proved), Proved),
|
|
1323
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == tested), Tested),
|
|
1324
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == asserted), Asserted),
|
|
1325
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == unverified), Unverified).
|
|
1326
|
+
|
|
1160
1327
|
req_row_fully_covered(Row) :-
|
|
1161
1328
|
CoverageStatus = Row.get(coverageStatus),
|
|
1162
1329
|
CoverageStatus = fully_covered.
|
|
@@ -1175,8 +1342,8 @@ req_row_missing_test(Row) :-
|
|
|
1175
1342
|
Row.gaps = [missing_test].
|
|
1176
1343
|
req_row_missing_both(Row) :-
|
|
1177
1344
|
Row.gaps = [missing_scenario_and_test].
|
|
1178
|
-
|
|
1179
|
-
Row.proofStatus ==
|
|
1345
|
+
req_row_proof_tested(Row) :-
|
|
1346
|
+
Row.proofStatus == tested.
|
|
1180
1347
|
req_row_proof_unresolved(Row) :-
|
|
1181
1348
|
Row.proofStatus == unresolved.
|
|
1182
1349
|
req_row_proof_missing(Row) :-
|
|
@@ -1377,7 +1544,7 @@ filter_req_coverage_rows(false, Rows, Filtered) :-
|
|
|
1377
1544
|
|
|
1378
1545
|
req_row_proof_passing(Row) :-
|
|
1379
1546
|
Status = Row.proofStatus,
|
|
1380
|
-
memberchk(Status, [
|
|
1547
|
+
memberchk(Status, [tested, not_applicable]).
|
|
1381
1548
|
|
|
1382
1549
|
filter_symbol_coverage_rows(true, Rows, Rows).
|
|
1383
1550
|
filter_symbol_coverage_rows(false, Rows, Filtered) :-
|
package/src/intervals.pl
CHANGED
|
@@ -30,6 +30,7 @@ numeric_constraints_satisfiable(Constraints) :-
|
|
|
30
30
|
% integer lies strictly between 0 and 1, and eq 0.5 has no integer solution.
|
|
31
31
|
% IntegerVariables may also be the atom `all`, making every variable integral.
|
|
32
32
|
numeric_constraints_satisfiable(Constraints, IntegerVariables) :-
|
|
33
|
+
maplist(finite_constraint, Constraints),
|
|
33
34
|
term_variables(Constraints, Variables),
|
|
34
35
|
forall(
|
|
35
36
|
member(Variable, Variables),
|
|
@@ -49,6 +50,16 @@ numeric_constraint_entailed(Premises, Constraint, IntegerVariables) :-
|
|
|
49
50
|
numeric_constraint_negation(Constraint, Negated),
|
|
50
51
|
\+ numeric_constraints_satisfiable([Negated|Premises], IntegerVariables).
|
|
51
52
|
|
|
53
|
+
% A NaN or infinite bound has no place on the number line the intervals
|
|
54
|
+
% model, so it is an error rather than an answer.
|
|
55
|
+
finite_constraint(c(_Op, _Variable, Value)) :-
|
|
56
|
+
( float(Value),
|
|
57
|
+
float_class(Value, Class),
|
|
58
|
+
memberchk(Class, [nan, infinite])
|
|
59
|
+
-> domain_error(finite_number, Value)
|
|
60
|
+
; true
|
|
61
|
+
).
|
|
62
|
+
|
|
52
63
|
integer_variable(_, all) :- !.
|
|
53
64
|
integer_variable(Variable, IntegerVariables) :-
|
|
54
65
|
is_list(IntegerVariables),
|
package/src/kb.pl
CHANGED
|
@@ -3121,6 +3121,13 @@ value_from_props(Props, string, Value) :- memberchk(value_string=ValueRaw, Props
|
|
|
3121
3121
|
value_from_props(Props, int, Value) :- memberchk(value_int=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3122
3122
|
value_from_props(Props, number, Value) :- memberchk(value_number=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3123
3123
|
value_from_props(Props, bool, Value) :- memberchk(value_bool=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3124
|
+
% A set value is its sorted, unique members, so two facts naming the same set
|
|
3125
|
+
% in any order compare equal.
|
|
3126
|
+
value_from_props(Props, set, Value) :-
|
|
3127
|
+
memberchk(value_set=ValueRaw, Props),
|
|
3128
|
+
normalize_term_atom_list(ValueRaw, Atoms),
|
|
3129
|
+
!,
|
|
3130
|
+
sort(Atoms, Value).
|
|
3124
3131
|
value_from_props(_, _, '').
|
|
3125
3132
|
|
|
3126
3133
|
%% unwrap_rdf_value(+Raw, -Value)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
% Module: kb_consistency
|
|
2
|
+
% implements REQ-kibi-kb-certificates
|
|
3
|
+
% The kb-consistency check rules. Each run produces the certificate bundle
|
|
4
|
+
% (certificates.pl), round-trips it through JSON exactly as a saved bundle
|
|
5
|
+
% would be read, and re-checks it with the independent checker
|
|
6
|
+
% (certificate_check.pl). Only what the checker accepts becomes a finding:
|
|
7
|
+
%
|
|
8
|
+
% kb-consistency a re-verified contradiction (an irreducible
|
|
9
|
+
% core of property facts, two facts that
|
|
10
|
+
% disagree on an atom or a decision, or a
|
|
11
|
+
% negation cycle among stored rules), or a
|
|
12
|
+
% certificate the checker rejects
|
|
13
|
+
% kb-consistency-unresolved a context the checker cannot compare (mixed
|
|
14
|
+
% types or units, unsupported operators or
|
|
15
|
+
% polarities, windows that never overlap)
|
|
16
|
+
:- module(kb_consistency, [
|
|
17
|
+
check_kb_consistency/1,
|
|
18
|
+
check_kb_consistency_unresolved/1
|
|
19
|
+
]).
|
|
20
|
+
|
|
21
|
+
:- encoding(utf8).
|
|
22
|
+
|
|
23
|
+
:- use_module(library(apply)).
|
|
24
|
+
:- use_module(library(lists)).
|
|
25
|
+
:- use_module(library(http/json)).
|
|
26
|
+
:- use_module('kb.pl', [kb_entity/3, kb_relationship/3]).
|
|
27
|
+
:- use_module('certificates.pl', [kb_certificates/1]).
|
|
28
|
+
:- use_module('certificate_check.pl', [verify_certificates/2]).
|
|
29
|
+
|
|
30
|
+
%% check_kb_consistency(-Violations)
|
|
31
|
+
check_kb_consistency(Violations) :-
|
|
32
|
+
consistency_verification(Bundle, Report),
|
|
33
|
+
numbered_certificates(Bundle, Numbered),
|
|
34
|
+
findall(Index, (member(Failure, Report.rejected), get_dict(certificate, Failure, Index)), Rejected),
|
|
35
|
+
findall(Violation,
|
|
36
|
+
( member(Index-Certificate, Numbered),
|
|
37
|
+
\+ memberchk(Index, Rejected),
|
|
38
|
+
contradiction_violation(Certificate, Violation)
|
|
39
|
+
),
|
|
40
|
+
Contradictions),
|
|
41
|
+
findall(Violation,
|
|
42
|
+
( member(Failure, Report.rejected),
|
|
43
|
+
rejected_violation(Failure, Violation)
|
|
44
|
+
),
|
|
45
|
+
RejectedViolations),
|
|
46
|
+
append(Contradictions, RejectedViolations, Unsorted),
|
|
47
|
+
sort(Unsorted, Violations).
|
|
48
|
+
|
|
49
|
+
%% check_kb_consistency_unresolved(-Violations)
|
|
50
|
+
check_kb_consistency_unresolved(Violations) :-
|
|
51
|
+
consistency_verification(_Bundle, Report),
|
|
52
|
+
findall(Violation,
|
|
53
|
+
( member(Theorem, Report.theorems),
|
|
54
|
+
get_dict(unresolved, Theorem, Entries),
|
|
55
|
+
member(Entry, Entries),
|
|
56
|
+
unresolved_violation(Theorem.theorem, Entry, Violation)
|
|
57
|
+
),
|
|
58
|
+
Unsorted),
|
|
59
|
+
sort(Unsorted, Violations).
|
|
60
|
+
|
|
61
|
+
consistency_verification(Bundle, Report) :-
|
|
62
|
+
kb_certificates(Produced),
|
|
63
|
+
with_output_to(string(Json), json_write_dict(current_output, Produced, [width(0)])),
|
|
64
|
+
open_string(Json, Stream),
|
|
65
|
+
json_read_dict(Stream, Bundle, [value_string_as(string)]),
|
|
66
|
+
verify_certificates(Bundle, Report).
|
|
67
|
+
|
|
68
|
+
numbered_certificates(Bundle, Numbered) :-
|
|
69
|
+
findall(Index-Certificate, nth1(Index, Bundle.certificates, Certificate), Numbered).
|
|
70
|
+
|
|
71
|
+
% ------------------------------------------------------------------
|
|
72
|
+
% Re-verified contradictions
|
|
73
|
+
% ------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
contradiction_violation(Certificate, violation('kb-consistency', EntityId, Description, Suggestion, "", _{certificate: Certificate})) :-
|
|
76
|
+
get_dict(kind, Certificate, KindText),
|
|
77
|
+
atom_string(Kind, KindText),
|
|
78
|
+
contradiction_text(Kind, Certificate, Facts, Description),
|
|
79
|
+
fact_requirements(Facts, Requirements),
|
|
80
|
+
entity_label(Requirements, Facts, EntityId),
|
|
81
|
+
suggestion_text(Kind, Requirements, Suggestion).
|
|
82
|
+
|
|
83
|
+
contradiction_text(property_contradiction, Certificate, Facts, Description) :-
|
|
84
|
+
Facts = Certificate.core,
|
|
85
|
+
context_text(Certificate, Context),
|
|
86
|
+
atomic_list_concat(Facts, ', ', FactText),
|
|
87
|
+
findall(Text,
|
|
88
|
+
( member(Drop, Certificate.dropModels),
|
|
89
|
+
format(string(Text), "~w: ~w", [Drop.factId, Drop.value])
|
|
90
|
+
),
|
|
91
|
+
DropTexts),
|
|
92
|
+
atomic_list_concat(DropTexts, ', ', DropText),
|
|
93
|
+
format(string(Description),
|
|
94
|
+
"Facts ~w cannot all hold for ~w: no value satisfies them together, and dropping any one leaves a value (~w)",
|
|
95
|
+
[FactText, Context, DropText]).
|
|
96
|
+
contradiction_text(predicate_contradiction, Certificate, Facts, Description) :-
|
|
97
|
+
Facts = Certificate.core,
|
|
98
|
+
Facts = [FactA, FactB],
|
|
99
|
+
atom_text(Certificate, Atom),
|
|
100
|
+
format(string(Description),
|
|
101
|
+
"Facts ~w and ~w give ~w opposite truth values", [FactA, FactB, Atom]).
|
|
102
|
+
contradiction_text(decision_contradiction, Certificate, Facts, Description) :-
|
|
103
|
+
Facts = Certificate.core,
|
|
104
|
+
Facts = [FactA, FactB],
|
|
105
|
+
decision_text(Certificate, Decision),
|
|
106
|
+
format(string(Description),
|
|
107
|
+
"Facts ~w and ~w decide ~w differently: one allows it and the other denies it", [FactA, FactB, Decision]).
|
|
108
|
+
contradiction_text(rule_negation_cycle, Certificate, [], Description) :-
|
|
109
|
+
findall(Text,
|
|
110
|
+
( member(Edge, Certificate.cycle),
|
|
111
|
+
signature_text(Edge.head, Head),
|
|
112
|
+
signature_text(Edge.body, Body),
|
|
113
|
+
( atom_string(negative, Edge.sign) -> Arrow = "depends on not" ; Arrow = "depends on" ),
|
|
114
|
+
format(string(Text), "~w ~w ~w", [Head, Arrow, Body])
|
|
115
|
+
),
|
|
116
|
+
Texts),
|
|
117
|
+
atomic_list_concat(Texts, "; ", CycleText),
|
|
118
|
+
format(string(Description),
|
|
119
|
+
"Stored rules are not stratified: a predicate depends on its own negation (~w)", [CycleText]).
|
|
120
|
+
|
|
121
|
+
suggestion_text(rule_negation_cycle, _Requirements,
|
|
122
|
+
"Break the cycle: change one rule so no predicate depends on its own negation, or supersede the requirement that stores it") :- !.
|
|
123
|
+
suggestion_text(_Kind, Requirements, Suggestion) :-
|
|
124
|
+
atomic_list_concat(Requirements, ', ', RequirementText),
|
|
125
|
+
format(string(Suggestion),
|
|
126
|
+
"Supersede or change one of ~w so the facts can hold together; kibi check --write-certificates <file> saves the certificate",
|
|
127
|
+
[RequirementText]).
|
|
128
|
+
|
|
129
|
+
% ------------------------------------------------------------------
|
|
130
|
+
% Rejected certificates and unresolved contexts
|
|
131
|
+
% ------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
rejected_violation(Failure, violation('kb-consistency', "kb-certificates", Description,
|
|
134
|
+
"Kibi produced a certificate its own checker rejects. Report it with the bundle from kibi check --write-certificates <file>", "")) :-
|
|
135
|
+
( get_dict(group, Failure, Group)
|
|
136
|
+
-> group_text(Group, Where)
|
|
137
|
+
; format(string(Where), "certificate ~w (~w)", [Failure.certificate, Failure.kind])
|
|
138
|
+
),
|
|
139
|
+
format(string(Description),
|
|
140
|
+
"The independent checker rejected ~w: ~w", [Where, Failure.reason]).
|
|
141
|
+
|
|
142
|
+
unresolved_violation(_Theorem, Entry, violation('kb-consistency-unresolved', EntityId, Description, Suggestion, "")) :-
|
|
143
|
+
group_text(Entry, Where),
|
|
144
|
+
( get_dict(reason, Entry, Reason)
|
|
145
|
+
-> true
|
|
146
|
+
; Reason = "the contradicting facts' validity windows never overlap"
|
|
147
|
+
),
|
|
148
|
+
entry_facts(Entry, Facts),
|
|
149
|
+
fact_requirements(Facts, Requirements),
|
|
150
|
+
entity_label(Requirements, Facts, EntityId0),
|
|
151
|
+
( EntityId0 == "" -> EntityId = Where ; EntityId = EntityId0 ),
|
|
152
|
+
format(string(Description),
|
|
153
|
+
"Kibi cannot decide whether the facts for ~w hold together: ~w. It never counts as consistent", [Where, Reason]),
|
|
154
|
+
Suggestion = "Align the facts' value_type, unit, operator or polarity so they can be compared, or accept that this context stays unchecked".
|
|
155
|
+
|
|
156
|
+
entry_facts(Entry, Facts) :-
|
|
157
|
+
get_dict(subjectKey, Entry, Subject),
|
|
158
|
+
!,
|
|
159
|
+
get_dict(propertyKey, Entry, Property),
|
|
160
|
+
findall(Fact,
|
|
161
|
+
( kb:current_req(Req),
|
|
162
|
+
kb:effective_req_property_fact(Req, Subject, Fact, Property, _, _, _, _, _, _, _, _)
|
|
163
|
+
),
|
|
164
|
+
Facts0),
|
|
165
|
+
sort(Facts0, Facts).
|
|
166
|
+
entry_facts(_Entry, []).
|
|
167
|
+
|
|
168
|
+
% ------------------------------------------------------------------
|
|
169
|
+
% Text helpers
|
|
170
|
+
% ------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
fact_requirements(Facts, Requirements) :-
|
|
173
|
+
findall(Req,
|
|
174
|
+
( member(FactText, Facts),
|
|
175
|
+
atom_string(Fact, FactText),
|
|
176
|
+
member(Type, [requires_property, requires_predicate, requires_rule]),
|
|
177
|
+
kb_relationship(Type, Req, Fact),
|
|
178
|
+
kb:current_req(Req)
|
|
179
|
+
),
|
|
180
|
+
Requirements0),
|
|
181
|
+
sort(Requirements0, Requirements).
|
|
182
|
+
|
|
183
|
+
entity_label([], [], "") :- !.
|
|
184
|
+
entity_label([], Facts, Label) :- !, atomic_list_concat(Facts, '/', Label0), atom_string(Label0, Label).
|
|
185
|
+
entity_label(Requirements, _Facts, Label) :- atomic_list_concat(Requirements, '/', Label0), atom_string(Label0, Label).
|
|
186
|
+
|
|
187
|
+
context_text(Label, Text) :-
|
|
188
|
+
( get_dict(scope, Label, Scope0) -> atom_string(Scope0, Scope) ; Scope = "" ),
|
|
189
|
+
( Scope == ""
|
|
190
|
+
-> format(string(Text), "~w.~w", [Label.subjectKey, Label.propertyKey])
|
|
191
|
+
; format(string(Text), "~w.~w (scope ~w)", [Label.subjectKey, Label.propertyKey, Scope])
|
|
192
|
+
).
|
|
193
|
+
|
|
194
|
+
group_text(Group, Text) :-
|
|
195
|
+
( get_dict(subjectKey, Group, _)
|
|
196
|
+
-> context_text(Group, Text)
|
|
197
|
+
; get_dict(args, Group, _)
|
|
198
|
+
-> atom_text(Group, Text)
|
|
199
|
+
; get_dict(key, Group, _)
|
|
200
|
+
-> decision_text(Group, Text)
|
|
201
|
+
; format(string(Text), "~w", [Group])
|
|
202
|
+
).
|
|
203
|
+
|
|
204
|
+
atom_text(Label, Text) :-
|
|
205
|
+
atomic_list_concat(Label.args, ', ', Args),
|
|
206
|
+
format(string(Text), "~w:~w(~w)", [Label.namespace, Label.name, Args]).
|
|
207
|
+
|
|
208
|
+
decision_text(Label, Text) :-
|
|
209
|
+
atomic_list_concat(Label.key, ', ', Key),
|
|
210
|
+
format(string(Text), "~w:~w/~w for (~w)", [Label.namespace, Label.name, Label.arity, Key]).
|
|
211
|
+
|
|
212
|
+
signature_text(Signature, Text) :-
|
|
213
|
+
format(string(Text), "~w:~w/~w", [Signature.namespace, Signature.name, Signature.arity]).
|