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/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
@@ -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),
@@ -1140,6 +1255,8 @@ coverage_summary(Rows, Summary) :-
1140
1255
  length(MissingBothRows, MissingScenarioAndTest),
1141
1256
  length(ProofTestedRows, ProofTested),
1142
1257
  evidence_level_counts(Rows, EvidenceLevels),
1258
+ claim_level_counts(Rows, ProofClaims),
1259
+ evidence_floor_counts(Rows, EvidenceFloors),
1143
1260
  length(ProofUnresolvedRows, ProofUnresolved),
1144
1261
  length(ProofMissingRows, ProofMissing),
1145
1262
  length(ProofNotApplicableRows, ProofNotApplicable),
@@ -1156,9 +1273,50 @@ coverage_summary(Rows, Summary) :-
1156
1273
  proofUnresolved: ProofUnresolved,
1157
1274
  proofMissing: ProofMissing,
1158
1275
  proofNotApplicable: ProofNotApplicable,
1159
- evidenceLevels: EvidenceLevels
1276
+ evidenceLevels: EvidenceLevels,
1277
+ proofClaims: ProofClaims,
1278
+ evidenceFloors: EvidenceFloors,
1279
+ linkCoverageScope: priority_must
1160
1280
  }.
1161
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
+
1162
1320
  % implements REQ-kibi-evidence-levels
1163
1321
  evidence_level_counts(Rows, _{proved: Proved, tested: Tested, asserted: Asserted, unverified: Unverified}) :-
1164
1322
  aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == proved), Proved),
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
@@ -72,6 +72,7 @@
72
72
  check_req_contradiction/1,
73
73
  normalize_term_atom/2,
74
74
  normalize_term_atom_list/2,
75
+ normalize_literal_atom_list/2,
75
76
  changeset/4, % Export for testing
76
77
  kb_uri/1
77
78
  ]).
@@ -85,7 +86,7 @@
85
86
  :- use_module(library(sha), [sha_hash/3, hash_atom/2]).
86
87
  :- use_module(library(http/json)).
87
88
  :- use_module(library(aggregate), [aggregate_all/3]).
88
- :- use_module(library(lists), [sum_list/2]).
89
+ :- use_module(library(lists), [sum_list/2, subtract/3, intersection/3]).
89
90
  :- use_module(library(ordsets)).
90
91
  :- use_module(library(solution_sequences), [distinct/1, limit/2, offset/2, order_by/2]).
91
92
  :- use_module('../schema/entities.pl', [entity_type/1, entity_property/3, required_property/2]).
@@ -3080,6 +3081,23 @@ normalize_term_atom_list(Raw, Atoms) :-
3080
3081
  !,
3081
3082
  maplist(normalize_term_atom, Value, Atoms).
3082
3083
 
3084
+ %% normalize_literal_atom_list(+Raw, -Atoms)
3085
+ % The members of a stored list as atoms, verbatim. Unlike
3086
+ % normalize_term_atom_list it keeps a member's '/' segments, so set members
3087
+ % such as rxjs/operators, GET /api/users or finance/de stay whole.
3088
+ normalize_literal_atom_list(Raw, Atoms) :-
3089
+ unwrap_rdf_value(Raw, Value),
3090
+ is_list(Value),
3091
+ maplist(normalize_literal_atom, Value, Atoms).
3092
+
3093
+ normalize_literal_atom(Raw, Atom) :-
3094
+ unwrap_rdf_value(Raw, Value),
3095
+ ( atom(Value) -> Atom = Value
3096
+ ; string(Value) -> atom_string(Atom, Value)
3097
+ ; number(Value) -> atom_number(Atom, Value)
3098
+ ; term_string(Value, String), atom_string(Atom, String)
3099
+ ).
3100
+
3083
3101
  normalize_term_integer(Raw, Integer) :-
3084
3102
  unwrap_rdf_value(Raw, Value),
3085
3103
  ( integer(Value)
@@ -3121,6 +3139,13 @@ value_from_props(Props, string, Value) :- memberchk(value_string=ValueRaw, Props
3121
3139
  value_from_props(Props, int, Value) :- memberchk(value_int=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
3122
3140
  value_from_props(Props, number, Value) :- memberchk(value_number=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
3123
3141
  value_from_props(Props, bool, Value) :- memberchk(value_bool=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
3142
+ % A set value is its sorted, unique members, so two facts naming the same set
3143
+ % in any order compare equal.
3144
+ value_from_props(Props, set, Value) :-
3145
+ memberchk(value_set=ValueRaw, Props),
3146
+ normalize_literal_atom_list(ValueRaw, Atoms),
3147
+ !,
3148
+ sort(Atoms, Value).
3124
3149
  value_from_props(_, _, '').
3125
3150
 
3126
3151
  %% unwrap_rdf_value(+Raw, -Value)
@@ -3219,6 +3244,35 @@ same_value(TypeA, ValA, TypeB, ValB) :-
3219
3244
  values_conflict(eq, ValA, eq, ValB, Type) :-
3220
3245
  \+ same_value(Type, ValA, Type, ValB).
3221
3246
 
3247
+ % A set fact with includes says every member of its set has the property;
3248
+ % eq on a set says exactly those members do. Two includes facts always agree
3249
+ % (the members having the property include both sets); includes conflicts
3250
+ % with eq only when it names a member the eq set leaves out.
3251
+ % implements REQ-kibi-non-numeric-proof-kinds
3252
+ values_conflict(includes, Included, eq, Exact, set) :-
3253
+ \+ subtract(Included, Exact, []).
3254
+ values_conflict(eq, Exact, includes, Included, set) :-
3255
+ \+ subtract(Included, Exact, []).
3256
+
3257
+ % subset_of says every member with the property is in its set, excludes that
3258
+ % no member of its set has it. subset_of conflicts with a set that must have
3259
+ % the property (eq or includes) when that set names a member outside it;
3260
+ % excludes conflicts when the two sets share a member. subset_of and excludes
3261
+ % never conflict with each other or themselves: the empty set satisfies them.
3262
+ % implements REQ-kibi-source-proof-kinds
3263
+ values_conflict(subset_of, Allowed, Op, Required, set) :-
3264
+ memberchk(Op, [eq, includes]),
3265
+ \+ subtract(Required, Allowed, []).
3266
+ values_conflict(Op, Required, subset_of, Allowed, set) :-
3267
+ memberchk(Op, [eq, includes]),
3268
+ \+ subtract(Required, Allowed, []).
3269
+ values_conflict(excludes, Excluded, Op, Required, set) :-
3270
+ memberchk(Op, [eq, includes]),
3271
+ intersection(Excluded, Required, [_|_]).
3272
+ values_conflict(Op, Required, excludes, Excluded, set) :-
3273
+ memberchk(Op, [eq, includes]),
3274
+ intersection(Excluded, Required, [_|_]).
3275
+
3222
3276
  % Eq/neq conflict on the same scalar value.
3223
3277
  values_conflict(eq, ValA, neq, ValB, Type) :-
3224
3278
  same_value(Type, ValA, Type, ValB).