kibi-core 0.26.1 → 0.27.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/src/checks.pl +178 -17
- package/src/claim_obligations.pl +16 -1
- package/src/code_certificates.pl +4 -0
- package/src/discovery.pl +65 -23
- package/src/intervals.pl +35 -14
- package/src/kb.pl +288 -36
- package/src/kb_consistency.pl +15 -0
- package/src/logic_ir.pl +130 -161
- package/src/model_check.pl +54 -6
- package/src/requirement_proof.pl +91 -10
- package/src/status.pl +16 -0
- package/src/units.pl +1 -3
package/src/model_check.pl
CHANGED
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
% routes. set_relation compares the members with the
|
|
48
48
|
% fact's set; required_fields and nullable_fields check
|
|
49
49
|
% each selected table or object; route_middleware checks
|
|
50
|
-
% each selected route's middleware
|
|
50
|
+
% each selected route's middleware; sequence_precedes
|
|
51
|
+
% checks each "A -> B" member against the list in the
|
|
52
|
+
% order read (every A before every B).
|
|
51
53
|
%
|
|
52
54
|
% Rules with explicit exists quantifiers, count or temporal expressions, or
|
|
53
55
|
% durations, timestamps and intervals are outside what Kibi enumerates and
|
|
@@ -115,14 +117,18 @@ certificate_model(state_machine, Certificate, model(any(States), Table, machine(
|
|
|
115
117
|
; Initial = none
|
|
116
118
|
),
|
|
117
119
|
machine_table(States, Edges, Initial, Table).
|
|
118
|
-
certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes))) :-
|
|
120
|
+
certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes, Sequence))) :-
|
|
119
121
|
!,
|
|
120
122
|
required(Certificate, model, Model),
|
|
121
123
|
required(Model, members, RawMembers),
|
|
122
124
|
maplist(text_string, RawMembers, Members0),
|
|
123
125
|
sort(Members0, Members),
|
|
124
126
|
( get_dict(entities, Model, Entities) -> true ; Entities = none ),
|
|
125
|
-
( get_dict(routes, Model, Routes) -> true ; Routes = none )
|
|
127
|
+
( get_dict(routes, Model, Routes) -> true ; Routes = none ),
|
|
128
|
+
( get_dict(sequence, Model, RawSequence)
|
|
129
|
+
-> maplist(text_string, RawSequence, Sequence)
|
|
130
|
+
; Sequence = none
|
|
131
|
+
).
|
|
126
132
|
certificate_model(Kind, _, _) :-
|
|
127
133
|
format(string(Reason), "a ~w certificate has no finite model to check", [Kind]),
|
|
128
134
|
throw(model_error(Reason)).
|
|
@@ -391,14 +397,14 @@ obligation_result("machine_relation", Obligation, model(_, _, Machine), Result)
|
|
|
391
397
|
; Result = ok
|
|
392
398
|
)
|
|
393
399
|
).
|
|
394
|
-
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _)), Result) :- !,
|
|
400
|
+
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _)), Result) :- !,
|
|
395
401
|
dict_text(Obligation, operator, Operator),
|
|
396
402
|
obligation_strings(Obligation, Expected),
|
|
397
403
|
( relation_problem(Operator, Actual, Expected, "the members Kibi read from the source", Reason)
|
|
398
404
|
-> Result = problem(Reason)
|
|
399
405
|
; Result = ok
|
|
400
406
|
).
|
|
401
|
-
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _)), Result) :-
|
|
407
|
+
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)), Result) :-
|
|
402
408
|
memberchk(FieldKind, ["required_fields", "nullable_fields"]),
|
|
403
409
|
!,
|
|
404
410
|
( is_list(Entities), Entities \== []
|
|
@@ -411,7 +417,7 @@ obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _)), Re
|
|
|
411
417
|
-> Result = problem(Reason)
|
|
412
418
|
; Result = ok
|
|
413
419
|
).
|
|
414
|
-
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes)), Result) :- !,
|
|
420
|
+
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _)), Result) :- !,
|
|
415
421
|
( is_list(Routes), Routes \== []
|
|
416
422
|
-> true
|
|
417
423
|
; throw(model_error("the source has no routes; read a route table for a middleware claim"))
|
|
@@ -429,9 +435,51 @@ obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Route
|
|
|
429
435
|
-> Result = problem(Reason)
|
|
430
436
|
; Result = ok
|
|
431
437
|
).
|
|
438
|
+
% implements REQ-kibi-source-proof-kinds
|
|
439
|
+
obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence)), Result) :- !,
|
|
440
|
+
( is_list(Sequence)
|
|
441
|
+
-> true
|
|
442
|
+
; throw(model_error("the source has no order; read a list for a precedes claim"))
|
|
443
|
+
),
|
|
444
|
+
obligation_strings(Obligation, Pairs),
|
|
445
|
+
( member(Pair, Pairs),
|
|
446
|
+
precedes_problem(Pair, Sequence, Reason)
|
|
447
|
+
-> Result = problem(Reason)
|
|
448
|
+
; Result = ok
|
|
449
|
+
).
|
|
432
450
|
obligation_result(Kind, _, _, problem(Reason)) :-
|
|
433
451
|
format(string(Reason), "a ~w obligation cannot be checked against a finite model", [Kind]).
|
|
434
452
|
|
|
453
|
+
% A precedes member "A -> B" holds when A and B are both in the list and
|
|
454
|
+
% every A comes before every B.
|
|
455
|
+
precedes_problem(Pair, Sequence, Reason) :-
|
|
456
|
+
( precedes_pair(Pair, First, Second)
|
|
457
|
+
-> findall(I, nth1(I, Sequence, First), FirstAt),
|
|
458
|
+
findall(I, nth1(I, Sequence, Second), SecondAt),
|
|
459
|
+
( FirstAt == []
|
|
460
|
+
-> format(string(Reason), "~w is not in the list Kibi read", [First])
|
|
461
|
+
; SecondAt == []
|
|
462
|
+
-> format(string(Reason), "~w is not in the list Kibi read", [Second])
|
|
463
|
+
; max_list(FirstAt, LastFirst),
|
|
464
|
+
min_list(SecondAt, FirstSecond),
|
|
465
|
+
LastFirst > FirstSecond
|
|
466
|
+
-> format(string(Reason), "~w comes before ~w in the list Kibi read (items ~w and ~w)",
|
|
467
|
+
[Second, First, FirstSecond, LastFirst])
|
|
468
|
+
)
|
|
469
|
+
; format(string(Reason), "~w is not spelled \"A -> B\"", [Pair])
|
|
470
|
+
).
|
|
471
|
+
|
|
472
|
+
precedes_pair(Pair, First, Second) :-
|
|
473
|
+
sub_string(Pair, Before, 4, After, " -> "),
|
|
474
|
+
sub_string(Pair, 0, Before, _, First0),
|
|
475
|
+
sub_string(Pair, _, After, 0, Second0),
|
|
476
|
+
\+ sub_string(Second0, _, _, _, " -> "),
|
|
477
|
+
normalize_space(string(First), First0),
|
|
478
|
+
normalize_space(string(Second), Second0),
|
|
479
|
+
First \== "",
|
|
480
|
+
Second \== "",
|
|
481
|
+
!.
|
|
482
|
+
|
|
435
483
|
obligation_strings(Obligation, Members) :-
|
|
436
484
|
get_dict(members, Obligation, Raw),
|
|
437
485
|
maplist(text_string, Raw, Members0),
|
package/src/requirement_proof.pl
CHANGED
|
@@ -54,7 +54,13 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
|
|
|
54
54
|
(catch(parse_time(CheckedAtAtom, CheckedAtStamp), _, fail) -> true ; CheckedAtStamp = -1),
|
|
55
55
|
normalize_integer(MaxAgeSeconds, MaxAge),
|
|
56
56
|
normalize_atom(BindingMode, BindingModeAtom),
|
|
57
|
+
% Everything else in the context follows from the KB, so the proof memo
|
|
58
|
+
% key needs only the caller's parameters.
|
|
59
|
+
variant_sha1(params(Snapshot, CheckedAtAtom, MaxAge, BindingModeAtom, TestBindings), ParamsKey),
|
|
60
|
+
variant_sha1(findings(Contradictions, ContradictionWitnesses, UnsafeRules, UnverifiableRules), FindingsKey),
|
|
57
61
|
Context = _{
|
|
62
|
+
proofMemoKey: ParamsKey,
|
|
63
|
+
ladderMemoKey: FindingsKey,
|
|
58
64
|
contradictions: Contradictions,
|
|
59
65
|
contradictionWitnesses: ContradictionWitnesses,
|
|
60
66
|
unsafeRules: UnsafeRules,
|
|
@@ -68,28 +74,86 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
|
|
|
68
74
|
}.
|
|
69
75
|
|
|
70
76
|
% implements REQ-kibi-conservative-requirement-proof
|
|
77
|
+
% A requirement's proof is kept per KB generation and proof parameters, so
|
|
78
|
+
% the evidence-floor rule, the coverage evidence and a coverage report in one
|
|
79
|
+
% check read each ladder once. Inside a transaction (what-if staging) the
|
|
80
|
+
% KB is not the generation's, so the proof is computed afresh.
|
|
81
|
+
requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
82
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
83
|
+
get_dict(proofMemoKey, Context, ParamsKey),
|
|
84
|
+
kb:index_verification_key(requirement_proof, IndexKey)
|
|
85
|
+
-> Key = key(IndexKey, ParamsKey),
|
|
86
|
+
( kb:kb_requirement_proof_memo(ReqId, Key, Proof0)
|
|
87
|
+
-> Proof = Proof0
|
|
88
|
+
; compute_requirement_proof(ReqId, ReqProps, Context, Proof),
|
|
89
|
+
( once(kb:kb_requirement_proof_memo(_, Stored, _)),
|
|
90
|
+
Stored \== Key
|
|
91
|
+
-> retractall(kb:kb_requirement_proof_memo(_, _, _))
|
|
92
|
+
; true
|
|
93
|
+
),
|
|
94
|
+
assertz(kb:kb_requirement_proof_memo(ReqId, Key, Proof))
|
|
95
|
+
)
|
|
96
|
+
; compute_requirement_proof(ReqId, ReqProps, Context, Proof)
|
|
97
|
+
).
|
|
98
|
+
|
|
99
|
+
% The stages before passing_e2e read the KB and the context's KB-derived
|
|
100
|
+
% findings (contradictions, unsafe and unverifiable rules) only, never the
|
|
101
|
+
% caller's snapshot, receipt bindings or checkedAt. A check passes a new
|
|
102
|
+
% checkedAt every run, so these stages are kept per KB generation and those
|
|
103
|
+
% findings apart from the full proof; inside a transaction they are computed
|
|
104
|
+
% afresh.
|
|
105
|
+
receipt_independent_stages(ReqId, ReqProps, Context, Ladder) :-
|
|
106
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
107
|
+
get_dict(ladderMemoKey, Context, FindingsKey),
|
|
108
|
+
kb:index_verification_key(requirement_ladder, IndexKey)
|
|
109
|
+
-> variant_sha1(ReqProps, PropsKey),
|
|
110
|
+
Key = key(IndexKey, FindingsKey, PropsKey),
|
|
111
|
+
( kb:kb_requirement_ladder_memo(ReqId, Key, Ladder0)
|
|
112
|
+
-> Ladder = Ladder0
|
|
113
|
+
; compute_receipt_independent_stages(ReqId, ReqProps, Context, Ladder),
|
|
114
|
+
( once(kb:kb_requirement_ladder_memo(_, key(Stored, _, _), _)),
|
|
115
|
+
Stored \== IndexKey
|
|
116
|
+
-> retractall(kb:kb_requirement_ladder_memo(_, _, _))
|
|
117
|
+
; true
|
|
118
|
+
),
|
|
119
|
+
retractall(kb:kb_requirement_ladder_memo(ReqId, _, _)),
|
|
120
|
+
assertz(kb:kb_requirement_ladder_memo(ReqId, Key, Ladder))
|
|
121
|
+
)
|
|
122
|
+
; compute_receipt_independent_stages(ReqId, ReqProps, Context, Ladder)
|
|
123
|
+
).
|
|
124
|
+
|
|
125
|
+
compute_receipt_independent_stages(ReqId, ReqProps, Context,
|
|
126
|
+
ladder(SemanticStage, Inventory, LogicStage, ContradictionStage,
|
|
127
|
+
ScenarioStage, ScenarioTestStage, ScenarioTests, SymbolSets)) :-
|
|
128
|
+
semantic_inventory_stage(ReqProps, SemanticStage, Inventory),
|
|
129
|
+
logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
|
|
130
|
+
contradiction_stage(ReqId, LogicStage.status, SemanticStage.status, Context, ContradictionStage),
|
|
131
|
+
scenario_stage(ReqId, ScenarioStage, ScenarioIds),
|
|
132
|
+
scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
|
|
133
|
+
production_symbol_sets(ReqId, SymbolSets).
|
|
134
|
+
|
|
71
135
|
% Exemption first: a current requirement explicitly marked proof_exempt with a
|
|
72
136
|
% reason is intentionally outside E2E-proof scope and must never fall through
|
|
73
137
|
% to the ladder (which would report misleading receipt gaps).
|
|
74
|
-
|
|
138
|
+
compute_requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
75
139
|
kb:current_req(ReqId),
|
|
76
140
|
proof_exempt(ReqProps, Reason),
|
|
77
141
|
!,
|
|
78
142
|
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
79
|
-
|
|
143
|
+
compute_requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
80
144
|
\+ kb:current_req(ReqId),
|
|
81
145
|
!,
|
|
82
146
|
applicability_exclusion_reason(ReqId, Reason),
|
|
83
147
|
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
scenario_stage(ReqId, ScenarioStage, ScenarioIds),
|
|
89
|
-
scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
|
|
148
|
+
compute_requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
149
|
+
receipt_independent_stages(ReqId, ReqProps, Context,
|
|
150
|
+
ladder(SemanticStage, Inventory, LogicStage, ContradictionStage,
|
|
151
|
+
ScenarioStage, ScenarioTestStage, ScenarioTests, SymbolSets)),
|
|
90
152
|
passing_e2e_stage(ScenarioTestStage, Context, PassingE2eStage, PassingE2eTests),
|
|
91
153
|
executable_symbol_stage(PassingE2eTests, ExecutableStage, ExecutableSymbols),
|
|
92
|
-
|
|
154
|
+
production_symbol_stage_from_sets(ReqId, SymbolSets, PassingE2eTests, ScenarioTests,
|
|
155
|
+
PassingE2eStage.receiptEvidence, Context,
|
|
156
|
+
ProductionStage, ProductionSymbols),
|
|
93
157
|
source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, CoordinateStage),
|
|
94
158
|
Stages = _{
|
|
95
159
|
semanticInventory: SemanticStage,
|
|
@@ -1211,6 +1275,11 @@ canonical_json_value(Value, Json) :-
|
|
|
1211
1275
|
maplist(canonical_json_value, Value, Parts),
|
|
1212
1276
|
atomics_to_string(Parts, ",", Body),
|
|
1213
1277
|
format(string(Json), "[~s]", [Body]).
|
|
1278
|
+
% JSON true, false and null read back as atoms; json_write would quote them,
|
|
1279
|
+
% so the hash would differ from the one the CLI recorded at ingest.
|
|
1280
|
+
canonical_json_value(Value, Json) :-
|
|
1281
|
+
memberchk(Value, [true, false, null]), !,
|
|
1282
|
+
atom_string(Value, Json).
|
|
1214
1283
|
canonical_json_value(Value, Json) :-
|
|
1215
1284
|
with_output_to(string(Json), json_write(current_output, Value, [width(0)])).
|
|
1216
1285
|
|
|
@@ -1375,6 +1444,14 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
|
|
|
1375
1444
|
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, [], Context, Stage, ProductionSymbols).
|
|
1376
1445
|
|
|
1377
1446
|
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Stage, ProductionSymbols) :-
|
|
1447
|
+
production_symbol_sets(ReqId, Sets),
|
|
1448
|
+
production_symbol_stage_from_sets(ReqId, Sets, PassingE2eTests, ScenarioTests,
|
|
1449
|
+
ReceiptEvidence, Context, Stage, ProductionSymbols).
|
|
1450
|
+
|
|
1451
|
+
% The implementing symbols of a requirement, split into structural
|
|
1452
|
+
% (type-shape with a unit-test contract) and production ones, with the
|
|
1453
|
+
% production symbols' coordinates: all read from the KB alone.
|
|
1454
|
+
production_symbol_sets(ReqId, symbol_sets(StructuralSymbols, ProductionSymbols, Coordinates)) :-
|
|
1378
1455
|
findall(SymbolId,
|
|
1379
1456
|
(kb_relationship(implements, SymbolId, ReqId),
|
|
1380
1457
|
kb_entity(SymbolId, symbol, _),
|
|
@@ -1383,13 +1460,17 @@ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence,
|
|
|
1383
1460
|
sort(Implementing0, ImplementingSymbols),
|
|
1384
1461
|
include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
|
|
1385
1462
|
exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
|
|
1463
|
+
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates).
|
|
1464
|
+
|
|
1465
|
+
production_symbol_stage_from_sets(ReqId, symbol_sets(StructuralSymbols, ProductionSymbols, Coordinates),
|
|
1466
|
+
PassingE2eTests, ScenarioTests, ReceiptEvidence, Context,
|
|
1467
|
+
Stage, ProductionSymbols) :-
|
|
1386
1468
|
include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredProduction),
|
|
1387
1469
|
structural_unit_receipts(StructuralSymbols, Context, UnitReceipts),
|
|
1388
1470
|
exclude(structural_symbol_covered(PassingE2eTests, UnitReceipts), StructuralSymbols, UncoveredStructural),
|
|
1389
1471
|
append(UncoveredProduction, UncoveredStructural, UncoveredSymbols),
|
|
1390
1472
|
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests,
|
|
1391
1473
|
UncoveredProduction, UncoveredStructural, Status, StatusReason),
|
|
1392
|
-
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
|
|
1393
1474
|
production_coverage_explanations(
|
|
1394
1475
|
ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
|
|
1395
1476
|
PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context, Explanations),
|
package/src/status.pl
CHANGED
|
@@ -203,7 +203,23 @@ kb_snapshot_time(SnapshotTime) :-
|
|
|
203
203
|
% made kb_status_json quadratic (paths x entities) and could wedge large KBs
|
|
204
204
|
% for minutes whenever mtime churn (e.g. a git checkout) marked whole lanes
|
|
205
205
|
% newer than the snapshot.
|
|
206
|
+
% The source -> entity id index reads every entity, so it is kept per KB
|
|
207
|
+
% generation (whose key names the attached store's graph): status runs it on
|
|
208
|
+
% each freshness probe while the KB is unchanged. Inside a transaction it is
|
|
209
|
+
% built afresh.
|
|
206
210
|
entity_source_id_index(Index) :-
|
|
211
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
212
|
+
kb:index_verification_key(status_source_index, Key)
|
|
213
|
+
-> ( kb:kb_status_source_index_memo(Key, Index0)
|
|
214
|
+
-> Index = Index0
|
|
215
|
+
; compute_entity_source_id_index(Index),
|
|
216
|
+
retractall(kb:kb_status_source_index_memo(_, _)),
|
|
217
|
+
assertz(kb:kb_status_source_index_memo(Key, Index))
|
|
218
|
+
)
|
|
219
|
+
; compute_entity_source_id_index(Index)
|
|
220
|
+
).
|
|
221
|
+
|
|
222
|
+
compute_entity_source_id_index(Index) :-
|
|
207
223
|
findall(RelativeSource-Id,
|
|
208
224
|
( kb_entity(Id, _Type, Props),
|
|
209
225
|
entity_source_property(Props, RawSource),
|
package/src/units.pl
CHANGED
|
@@ -120,13 +120,11 @@ numeric_value_type(number).
|
|
|
120
120
|
|
|
121
121
|
known_unit(Unit, BaseUnit, Factor) :-
|
|
122
122
|
atom(Unit),
|
|
123
|
-
Unit \== '',
|
|
124
123
|
( unit_symbol(Unit, BaseUnit, Factor)
|
|
125
124
|
-> true
|
|
126
125
|
; downcase_atom(Unit, Lower),
|
|
127
126
|
unit_word(Lower, BaseUnit, Factor)
|
|
128
|
-
)
|
|
129
|
-
!.
|
|
127
|
+
).
|
|
130
128
|
|
|
131
129
|
% Integers (including big integers) stay exact. Floats have binary noise
|
|
132
130
|
% rounded away (0.1 h * 3600 = 360.00000000000006) before deciding whether the
|