kibi-core 0.24.0 → 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/schema/validation.pl +3 -1
- package/src/certificate_check.pl +38 -4
- package/src/certificates.pl +10 -2
- package/src/checks.pl +178 -17
- package/src/claim_obligations.pl +371 -28
- package/src/code_certificates.pl +243 -4
- package/src/discovery.pl +65 -23
- package/src/intervals.pl +35 -14
- package/src/kb.pl +337 -38
- package/src/kb_consistency.pl +15 -0
- package/src/logic_ir.pl +130 -161
- package/src/model_check.pl +776 -0
- package/src/requirement_proof.pl +101 -13
- package/src/rule_registry.pl +1 -1
- package/src/status.pl +16 -0
- package/src/units.pl +1 -3
package/package.json
CHANGED
package/schema/validation.pl
CHANGED
|
@@ -157,6 +157,8 @@ validate_fact_shape(property_value, Props) :-
|
|
|
157
157
|
valid_operator(Op),
|
|
158
158
|
memberchk(value_type=VT, Props),
|
|
159
159
|
valid_value_type(VT),
|
|
160
|
+
( Op == matches -> VT == string ; true ),
|
|
161
|
+
( memberchk(Op, [includes, subset_of, excludes]) -> VT == set ; true ),
|
|
160
162
|
exactly_one_value_field(Props),
|
|
161
163
|
value_type_matches_field(VT, Props),
|
|
162
164
|
valid_optional_fact_enums(Props),
|
|
@@ -225,7 +227,7 @@ text_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
|
225
227
|
text_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|
|
226
228
|
|
|
227
229
|
% valid_operator(+Op)
|
|
228
|
-
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]), !.
|
|
230
|
+
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte, matches, includes, subset_of, excludes]), !.
|
|
229
231
|
|
|
230
232
|
% valid_value_type(+VT)
|
|
231
233
|
valid_value_type(ValueType) :- memberchk(ValueType, [string, int, number, bool, set]), !.
|
package/src/certificate_check.pl
CHANGED
|
@@ -204,7 +204,7 @@ context_domain(Members, Domain, Reason) :-
|
|
|
204
204
|
; Types == [string]
|
|
205
205
|
-> Domain = string, context_scalar_reason(Members, Reason)
|
|
206
206
|
; Types == [set]
|
|
207
|
-
-> Domain = set,
|
|
207
|
+
-> Domain = set, context_set_reason(Members, Reason)
|
|
208
208
|
; Types = [_, _|_]
|
|
209
209
|
-> Domain = none, Reason = "the facts use different value types"
|
|
210
210
|
; Domain = none, Reason = "the value type is not supported"
|
|
@@ -228,6 +228,25 @@ context_scalar_reason(Members, Reason) :-
|
|
|
228
228
|
; Reason = none
|
|
229
229
|
).
|
|
230
230
|
|
|
231
|
+
% Sets also compare with includes (every member of the bound has the
|
|
232
|
+
% property), subset_of (every member with the property is in the bound) and
|
|
233
|
+
% excludes (no member of the bound has it), but only as requirements: a
|
|
234
|
+
% forbidden one has no single negation among the set operators. neq beside
|
|
235
|
+
% subset_of or excludes is left unresolved: the finite candidates below decide
|
|
236
|
+
% those only without neq.
|
|
237
|
+
context_set_reason(Members, Reason) :-
|
|
238
|
+
( member(_-tuple(_, _, Op, _, _, _, _, Polarity, _, _), Members),
|
|
239
|
+
( \+ memberchk(Op, [eq, neq, includes, subset_of, excludes])
|
|
240
|
+
; memberchk(Op, [includes, subset_of, excludes]), Polarity == forbid
|
|
241
|
+
)
|
|
242
|
+
-> Reason = "only eq, neq and a required includes, subset_of or excludes compare set values"
|
|
243
|
+
; member(_-tuple(_, _, neq, _, _, _, _, _, _, _), Members),
|
|
244
|
+
member(_-tuple(_, _, Bounding, _, _, _, _, _, _, _), Members),
|
|
245
|
+
memberchk(Bounding, [subset_of, excludes])
|
|
246
|
+
-> Reason = "neq beside subset_of or excludes on one set property is not compared"
|
|
247
|
+
; Reason = none
|
|
248
|
+
).
|
|
249
|
+
|
|
231
250
|
context_member(_Domain, Fact-tuple(_, _, Op0, _, Value, _, _, Polarity, From, To), m(Fact, Op, Value, From, To)) :-
|
|
232
251
|
( Polarity == forbid
|
|
233
252
|
-> numeric_constraint_negation(c(Op0, _, Value), c(Op, _, _))
|
|
@@ -250,13 +269,17 @@ certificate_member_holds(Domain, m(_, Op, Bound, _, _), Value) :-
|
|
|
250
269
|
; Op == neq -> ValueText \== BoundText
|
|
251
270
|
).
|
|
252
271
|
|
|
253
|
-
% A set value is the exact set of
|
|
254
|
-
% members in any order, neq for any other set
|
|
272
|
+
% A set value is the exact set of members with the property: eq holds for
|
|
273
|
+
% the same members in any order, neq for any other set, includes for any set
|
|
274
|
+
% that contains every member of the bound.
|
|
255
275
|
certificate_member_holds(set, m(_, Op, Bound, _, _), Value) :-
|
|
256
276
|
set_texts(Value, ValueTexts),
|
|
257
277
|
set_texts(Bound, BoundTexts),
|
|
258
278
|
( Op == eq -> ValueTexts == BoundTexts
|
|
259
279
|
; Op == neq -> ValueTexts \== BoundTexts
|
|
280
|
+
; Op == includes -> subtract(BoundTexts, ValueTexts, [])
|
|
281
|
+
; Op == subset_of -> subtract(ValueTexts, BoundTexts, [])
|
|
282
|
+
; Op == excludes -> \+ ( member(Text, ValueTexts), memberchk(Text, BoundTexts) )
|
|
260
283
|
).
|
|
261
284
|
|
|
262
285
|
%% set_texts(+Value, -Texts)
|
|
@@ -308,7 +331,18 @@ scalar_candidates(set, Members, Candidates) :-
|
|
|
308
331
|
sort(Texts0, Texts),
|
|
309
332
|
atomic_list_concat(["kibi:fresh"|Texts], '|', FreshAtom),
|
|
310
333
|
atom_string(FreshAtom, Fresh),
|
|
311
|
-
|
|
334
|
+
% The union of every bound satisfies any set of includes facts alone; the
|
|
335
|
+
% union of the includes bounds is the least set every includes fact
|
|
336
|
+
% allows, so with subset_of and excludes (and no eq or neq) it is
|
|
337
|
+
% satisfiable exactly when that union is.
|
|
338
|
+
includes_union(Members, Included),
|
|
339
|
+
append(Sets, [Texts, Included, [Fresh]], Candidates).
|
|
340
|
+
|
|
341
|
+
includes_union(Members, Union) :-
|
|
342
|
+
findall(Text,
|
|
343
|
+
(member(m(_, includes, Bound, _, _), Members), set_texts(Bound, Set), member(Text, Set)),
|
|
344
|
+
Texts),
|
|
345
|
+
sort(Texts, Union).
|
|
312
346
|
|
|
313
347
|
property_theorem(Contexts, Numbered, Theorem, Used, Rejected) :-
|
|
314
348
|
findall(Index-Certificate,
|
package/src/certificates.pl
CHANGED
|
@@ -125,9 +125,17 @@ model_candidates(string, Members, Candidates) :-
|
|
|
125
125
|
model_candidates(set, Members, Candidates) :-
|
|
126
126
|
findall(Set, (member(m(_, _, Bound, _, _), Members), set_texts(Bound, Set)), Sets0),
|
|
127
127
|
sort(Sets0, Sets),
|
|
128
|
-
findall(Text, (member(Set, Sets), member(Text, Set)),
|
|
128
|
+
findall(Text, (member(Set, Sets), member(Text, Set)), Texts0),
|
|
129
|
+
sort(Texts0, Texts),
|
|
129
130
|
fresh_text(Texts, 0, Fresh),
|
|
130
|
-
|
|
131
|
+
% The union of every bound satisfies any set of includes facts alone;
|
|
132
|
+
% the union of the includes bounds alone is the least set they allow,
|
|
133
|
+
% which subset_of and excludes facts accept when anything does.
|
|
134
|
+
findall(Text,
|
|
135
|
+
(member(m(_, includes, Bound, _, _), Members), set_texts(Bound, Set), member(Text, Set)),
|
|
136
|
+
Included0),
|
|
137
|
+
sort(Included0, Included),
|
|
138
|
+
append(Sets, [Texts, Included, [Fresh]], Candidates).
|
|
131
139
|
|
|
132
140
|
fresh_text(Bounds, Index, Fresh) :-
|
|
133
141
|
format(string(Candidate), "kibi:model:~w", [Index]),
|
package/src/checks.pl
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
:- module(checks, [
|
|
7
7
|
check_all/1, % Returns all violations as a dict
|
|
8
8
|
check_all_json/1, % Returns all violations as JSON string
|
|
9
|
+
check_all_json_file/2, % Writes all violations as JSON to a file
|
|
9
10
|
check_selected_json/2, % Returns only selected rule violations as JSON
|
|
11
|
+
check_selected_json_file/2, % Writes selected rule violations as JSON to a file
|
|
12
|
+
check_all_json_transport/4, % File transport with an answer fallback
|
|
13
|
+
check_selected_json_transport/4, % Same, for selected rules
|
|
10
14
|
check_must_priority_coverage/1, % Returns list of must-priority violations
|
|
11
15
|
check_symbol_coverage/1, % Returns list of uncovered symbols
|
|
12
16
|
check_changed_symbol_coverage/2, % symbol-coverage restricted to a staged change set
|
|
@@ -470,7 +474,7 @@ parse_json_object(Dict, Dict) :-
|
|
|
470
474
|
!.
|
|
471
475
|
parse_json_object(Raw, Dict) :-
|
|
472
476
|
(atom(Raw) ; string(Raw)),
|
|
473
|
-
|
|
477
|
+
json_text_term(Raw, Dict),
|
|
474
478
|
is_dict(Dict).
|
|
475
479
|
|
|
476
480
|
parse_json_list(Raw, Entries) :-
|
|
@@ -482,9 +486,48 @@ parse_json_list(^^(Value, _), Entries) :-
|
|
|
482
486
|
parse_json_list(Value, Entries).
|
|
483
487
|
parse_json_list(Raw, Entries) :-
|
|
484
488
|
(atom(Raw) ; string(Raw)),
|
|
485
|
-
|
|
489
|
+
json_text_term(Raw, Entries),
|
|
486
490
|
is_list(Entries).
|
|
487
491
|
|
|
492
|
+
% The same JSON text (a rule body, a claim inventory, a receipt history) is
|
|
493
|
+
% parsed by several rules and by every requirement that links the fact, so
|
|
494
|
+
% the parsed term is kept by its text. Parsing is a pure function of the
|
|
495
|
+
% text, so the memo never goes stale; it is bounded so a long-lived engine
|
|
496
|
+
% cannot grow it without limit.
|
|
497
|
+
:- dynamic json_text_memo/2.
|
|
498
|
+
:- dynamic json_text_memo_size/1.
|
|
499
|
+
|
|
500
|
+
% mutalog-disable-next-line literal: the bound is a tuning value; any size gives the same answers
|
|
501
|
+
json_text_memo_limit(16384).
|
|
502
|
+
|
|
503
|
+
json_text_term(Raw, Term) :-
|
|
504
|
+
atom_string(Text, Raw),
|
|
505
|
+
( json_text_memo(Text, Stored)
|
|
506
|
+
-> Stored = parsed(Term)
|
|
507
|
+
; ( catch(atom_json_dict(Text, Parsed, [value_string_as(string)]), _, fail)
|
|
508
|
+
-> Stored = parsed(Parsed)
|
|
509
|
+
; Stored = invalid
|
|
510
|
+
),
|
|
511
|
+
json_text_remember(Text, Stored),
|
|
512
|
+
Stored = parsed(Term)
|
|
513
|
+
).
|
|
514
|
+
|
|
515
|
+
json_text_remember(Text, Stored) :-
|
|
516
|
+
( json_text_memo_size(Size)
|
|
517
|
+
-> true
|
|
518
|
+
; Size = 0
|
|
519
|
+
),
|
|
520
|
+
json_text_memo_limit(Limit),
|
|
521
|
+
( Size >= Limit
|
|
522
|
+
-> retractall(json_text_memo(_, _)),
|
|
523
|
+
retractall(json_text_memo_size(_)),
|
|
524
|
+
Next = 1
|
|
525
|
+
; retractall(json_text_memo_size(_)),
|
|
526
|
+
Next is Size + 1
|
|
527
|
+
),
|
|
528
|
+
assertz(json_text_memo(Text, Stored)),
|
|
529
|
+
assertz(json_text_memo_size(Next)).
|
|
530
|
+
|
|
488
531
|
%% check_no_dangling_refs(-Violations)
|
|
489
532
|
% Finds all relationships referencing non-existent entities.
|
|
490
533
|
check_no_dangling_refs(Violations) :-
|
|
@@ -1281,7 +1324,28 @@ analysis_outcome(analysis([], [], false, []), feasible).
|
|
|
1281
1324
|
% advisory reasons in reporting order; ByException is true when some check is
|
|
1282
1325
|
% satisfiable only because an approved exception waived a constraint;
|
|
1283
1326
|
% NotApplicable is non-empty only when no assumption is governed at all.
|
|
1284
|
-
|
|
1327
|
+
% The analysis of a scenario is kept per KB generation: the blocking rule,
|
|
1328
|
+
% the unknown-feasibility rule and every requirement's proof ladder ask for
|
|
1329
|
+
% the same scenarios. Inside a transaction (what-if staging) the KB is not
|
|
1330
|
+
% the generation's, so the analysis is computed afresh.
|
|
1331
|
+
scenario_feasibility_analysis(ScenarioId, Analysis) :-
|
|
1332
|
+
( nonvar(ScenarioId),
|
|
1333
|
+
\+ rdf_db:rdf_active_transaction(_),
|
|
1334
|
+
kb:index_verification_key(scenario_feasibility, Key)
|
|
1335
|
+
-> ( kb:kb_scenario_feasibility_memo(ScenarioId, Key, Analysis0)
|
|
1336
|
+
-> Analysis = Analysis0
|
|
1337
|
+
; compute_scenario_feasibility_analysis(ScenarioId, Analysis),
|
|
1338
|
+
( once(kb:kb_scenario_feasibility_memo(_, Stored, _)),
|
|
1339
|
+
Stored \== Key
|
|
1340
|
+
-> retractall(kb:kb_scenario_feasibility_memo(_, _, _))
|
|
1341
|
+
; true
|
|
1342
|
+
),
|
|
1343
|
+
assertz(kb:kb_scenario_feasibility_memo(ScenarioId, Key, Analysis))
|
|
1344
|
+
)
|
|
1345
|
+
; compute_scenario_feasibility_analysis(ScenarioId, Analysis)
|
|
1346
|
+
).
|
|
1347
|
+
|
|
1348
|
+
compute_scenario_feasibility_analysis(ScenarioId, analysis(Witnesses, Unknowns, ByException, NotApplicable)) :-
|
|
1285
1349
|
findall(FactId, kb_relationship(assumes, ScenarioId, FactId), Assumed0),
|
|
1286
1350
|
sort(Assumed0, Assumed),
|
|
1287
1351
|
( Assumed == []
|
|
@@ -2526,7 +2590,24 @@ check_domain_contradictions_and_witnesses(Violations, Witnesses) :-
|
|
|
2526
2590
|
% Exact, JSON-safe evidence for every strict-property, ground-predicate, and
|
|
2527
2591
|
% safe-rule contradiction result. Rule overlap that cannot be proven or
|
|
2528
2592
|
% disproven remains status=unresolved rather than becoming consistency.
|
|
2593
|
+
%
|
|
2594
|
+
% kb_check, kb_coverage and the proof context each ask for every witness, so
|
|
2595
|
+
% the result is memoized per KB generation, like the entity property memo.
|
|
2596
|
+
% Inside an RDF transaction (the what-if staging) it is always recomputed.
|
|
2529
2597
|
check_domain_contradiction_witnesses(Witnesses) :-
|
|
2598
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
2599
|
+
kb:index_verification_key(domain_contradictions, Key)
|
|
2600
|
+
-> ( kb:kb_contradiction_witness_memo(Key, Cached)
|
|
2601
|
+
-> Witnesses = Cached
|
|
2602
|
+
; compute_domain_contradiction_witnesses(Computed),
|
|
2603
|
+
retractall(kb:kb_contradiction_witness_memo(_, _)),
|
|
2604
|
+
assertz(kb:kb_contradiction_witness_memo(Key, Computed)),
|
|
2605
|
+
Witnesses = Computed
|
|
2606
|
+
)
|
|
2607
|
+
; compute_domain_contradiction_witnesses(Witnesses)
|
|
2608
|
+
).
|
|
2609
|
+
|
|
2610
|
+
compute_domain_contradiction_witnesses(Witnesses) :-
|
|
2530
2611
|
findall(Witness, req_conflict_witness(_ReqA, _ReqB, Witness), GroundWitnesses),
|
|
2531
2612
|
functional_predicate_declarations(Functional),
|
|
2532
2613
|
findall(Witness, rule_contradiction_witness(Functional, Witness), RuleWitnesses),
|
|
@@ -2701,22 +2782,42 @@ opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB) :-
|
|
|
2701
2782
|
% Each unordered pair of (requirement, rule fact) endpoints is compared once,
|
|
2702
2783
|
% ordered by the requirement-fact pair: ordering requirements and facts
|
|
2703
2784
|
% independently would never compare REQ-A->FACT-Z with REQ-Z->FACT-A.
|
|
2785
|
+
%
|
|
2786
|
+
% Only current requirements with requires_rule links can pair, so the pairs
|
|
2787
|
+
% are drawn from that list (in current_req/1 order, each with its rule links
|
|
2788
|
+
% in relationship order) rather than from every pair of current
|
|
2789
|
+
% requirements; solutions and their order are unchanged.
|
|
2704
2790
|
opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB) :-
|
|
2705
|
-
|
|
2706
|
-
|
|
2791
|
+
rule_requirement_links(Links),
|
|
2792
|
+
member(ReqA-RulesA, Links),
|
|
2793
|
+
member(ReqB-RulesB, Links),
|
|
2707
2794
|
ReqA \== ReqB,
|
|
2708
|
-
|
|
2709
|
-
|
|
2795
|
+
member(FactA-RuleA, RulesA),
|
|
2796
|
+
member(FactB-RuleB, RulesB),
|
|
2710
2797
|
FactA \== FactB,
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2798
|
+
% mutalog-disable-next-line compare: equal endpoints need ReqA == ReqB, which is excluded above
|
|
2799
|
+
ReqA-FactA @< ReqB-FactB.
|
|
2800
|
+
|
|
2801
|
+
% Each requirement's rule facts with their parsed rules, read once rather
|
|
2802
|
+
% than once per pair: a link to a fact that is not a rule, or whose rule does
|
|
2803
|
+
% not parse, never pairs.
|
|
2804
|
+
rule_requirement_links(Links) :-
|
|
2805
|
+
findall(ReqId-Rules,
|
|
2806
|
+
( kb:current_req(ReqId),
|
|
2807
|
+
findall(FactId-Rule,
|
|
2808
|
+
( kb_relationship(requires_rule, ReqId, FactId),
|
|
2809
|
+
rule_fact_rule(FactId, Rule) ),
|
|
2810
|
+
Rules),
|
|
2811
|
+
% mutalog-disable-next-line goal_true,literal: a requirement without rules never pairs; this only prunes
|
|
2812
|
+
Rules \== [] ),
|
|
2813
|
+
Links).
|
|
2814
|
+
|
|
2815
|
+
rule_fact_rule(FactId, Rule) :-
|
|
2816
|
+
kb_entity(FactId, fact, Props),
|
|
2817
|
+
memberchk(fact_kind=Kind, Props),
|
|
2818
|
+
% mutalog-disable-next-line goal_true: only a rule fact carries a rule body to decode
|
|
2819
|
+
normalize_term_atom(Kind, rule),
|
|
2820
|
+
logic_rule_from_props(Props, Rule).
|
|
2720
2821
|
|
|
2721
2822
|
rule_contradiction_witness(Functional, Witness) :-
|
|
2722
2823
|
opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB),
|
|
@@ -3472,7 +3573,7 @@ inventory_entries(Raw, Entries) :-
|
|
|
3472
3573
|
( is_list(Raw) -> Entries = Raw
|
|
3473
3574
|
; Raw = ^^(Value, _) -> inventory_entries(Value, Entries)
|
|
3474
3575
|
; (atom(Raw) ; string(Raw)),
|
|
3475
|
-
|
|
3576
|
+
json_text_term(Raw, JsonEntries),
|
|
3476
3577
|
is_list(JsonEntries)
|
|
3477
3578
|
-> Entries = JsonEntries
|
|
3478
3579
|
; (atom(Raw) ; string(Raw)),
|
|
@@ -3748,6 +3849,66 @@ check_all_json_with_options(JsonString, RequireAdr) :-
|
|
|
3748
3849
|
JsonString
|
|
3749
3850
|
).
|
|
3750
3851
|
|
|
3852
|
+
%% check_all_json_file(+Path, +RequireAdr)
|
|
3853
|
+
% Writes the JSON check_all_json_with_options/2 would answer to Path.
|
|
3854
|
+
% The answer itself carries no violations, so a large KB's report is never
|
|
3855
|
+
% bounded by the engine's output capacity; the caller reads the file.
|
|
3856
|
+
check_all_json_file(Path, RequireAdr) :-
|
|
3857
|
+
check_all_with_options(ViolationsDict, RequireAdr),
|
|
3858
|
+
violations_json_to_file(ViolationsDict, Path).
|
|
3859
|
+
|
|
3860
|
+
%% check_selected_json_file(+Rules, +Path)
|
|
3861
|
+
% File-transport form of check_selected_json/2.
|
|
3862
|
+
check_selected_json_file(Rules, Path) :-
|
|
3863
|
+
check_selected(Rules, ViolationsDict),
|
|
3864
|
+
violations_json_to_file(ViolationsDict, Path).
|
|
3865
|
+
|
|
3866
|
+
%% check_all_json_transport(+Path, +RequireAdr, -Transport, -JsonString)
|
|
3867
|
+
% Writes the violations JSON to Path (Transport = file, JsonString = ''), or
|
|
3868
|
+
% answers it as JsonString (Transport = answer) when Path is `none` or the
|
|
3869
|
+
% file cannot be written: a full or read-only temp folder, or an engine that
|
|
3870
|
+
% cannot reach the caller's temp folder, must not fail a check that fits in
|
|
3871
|
+
% an answer.
|
|
3872
|
+
check_all_json_transport(Path, RequireAdr, Transport, JsonString) :-
|
|
3873
|
+
check_all_with_options(ViolationsDict, RequireAdr),
|
|
3874
|
+
violations_json_transport(ViolationsDict, Path, Transport, JsonString).
|
|
3875
|
+
|
|
3876
|
+
%% check_selected_json_transport(+Rules, +Path, -Transport, -JsonString)
|
|
3877
|
+
% check_all_json_transport/4 for the named rules only.
|
|
3878
|
+
check_selected_json_transport(Rules, Path, Transport, JsonString) :-
|
|
3879
|
+
check_selected(Rules, ViolationsDict),
|
|
3880
|
+
violations_json_transport(ViolationsDict, Path, Transport, JsonString).
|
|
3881
|
+
|
|
3882
|
+
violations_json_transport(ViolationsDict, Path, Transport, JsonString) :-
|
|
3883
|
+
( Path \== none,
|
|
3884
|
+
catch(violations_json_to_file(ViolationsDict, Path), Error,
|
|
3885
|
+
( file_transport_error(Error)
|
|
3886
|
+
-> fail
|
|
3887
|
+
; throw(Error)
|
|
3888
|
+
))
|
|
3889
|
+
-> Transport = file,
|
|
3890
|
+
JsonString = ''
|
|
3891
|
+
; Transport = answer,
|
|
3892
|
+
violations_dict_to_json(ViolationsDict, JsonDict),
|
|
3893
|
+
with_output_to_string(
|
|
3894
|
+
json_write_dict(current_output, JsonDict, [width(0)]),
|
|
3895
|
+
JsonString
|
|
3896
|
+
)
|
|
3897
|
+
).
|
|
3898
|
+
|
|
3899
|
+
file_transport_error(error(permission_error(_, _, _), _)).
|
|
3900
|
+
file_transport_error(error(existence_error(_, _), _)).
|
|
3901
|
+
file_transport_error(error(resource_error(_), _)).
|
|
3902
|
+
file_transport_error(error(io_error(_, _), _)).
|
|
3903
|
+
|
|
3904
|
+
violations_json_to_file(ViolationsDict, Path) :-
|
|
3905
|
+
violations_dict_to_json(ViolationsDict, JsonDict),
|
|
3906
|
+
setup_call_cleanup(
|
|
3907
|
+
open(Path, write, Stream, [encoding(utf8)]),
|
|
3908
|
+
json_write_dict(Stream, JsonDict, [width(0)]),
|
|
3909
|
+
close(Stream)
|
|
3910
|
+
).
|
|
3911
|
+
|
|
3751
3912
|
%% check_all_with_options(-ViolationsDict, +RequireAdr)
|
|
3752
3913
|
% Returns a dict with all violations, respecting options.
|
|
3753
3914
|
check_all_with_options(ViolationsDict, RequireAdr) :-
|