kibi-core 0.7.0 → 0.7.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 +4 -0
- package/schema/validation.pl +22 -1
- package/src/checks.pl +134 -2
- package/src/kb.pl +22 -1
- package/src/sparql_client.pl +23 -1
- package/src/status.pl +9 -0
package/package.json
CHANGED
package/schema/entities.pl
CHANGED
|
@@ -29,6 +29,7 @@ entity_property(_, severity, atom).
|
|
|
29
29
|
entity_property(_, links, list).
|
|
30
30
|
entity_property(_, text_ref, uri).
|
|
31
31
|
entity_property(_, sourceFile, uri).
|
|
32
|
+
entity_property(req, logic_claims, list).
|
|
32
33
|
|
|
33
34
|
% Typed fact fields - only valid for fact entities
|
|
34
35
|
entity_property(fact, fact_kind, atom).
|
|
@@ -47,6 +48,8 @@ entity_property(fact, closed_world, boolean).
|
|
|
47
48
|
entity_property(fact, valid_from, datetime).
|
|
48
49
|
entity_property(fact, valid_to, datetime).
|
|
49
50
|
entity_property(fact, canonical_key, string).
|
|
51
|
+
entity_property(fact, claim_key, string).
|
|
52
|
+
entity_property(fact, claim_text, string).
|
|
50
53
|
entity_property(fact, predicate_name, string).
|
|
51
54
|
entity_property(fact, predicate_namespace, string).
|
|
52
55
|
entity_property(fact, predicate_arity, integer).
|
|
@@ -80,6 +83,7 @@ optional_property(Type, priority) :- entity_type(Type).
|
|
|
80
83
|
optional_property(Type, severity) :- entity_type(Type).
|
|
81
84
|
optional_property(Type, links) :- entity_type(Type).
|
|
82
85
|
optional_property(Type, text_ref) :- entity_type(Type).
|
|
86
|
+
optional_property(req, logic_claims).
|
|
83
87
|
optional_property(test, verification_scope).
|
|
84
88
|
optional_property(test, verification_perspective).
|
|
85
89
|
|
package/schema/validation.pl
CHANGED
|
@@ -59,17 +59,24 @@ validate_entity_shape(fact, Props) :-
|
|
|
59
59
|
!,
|
|
60
60
|
valid_optional_fact_enums(Props),
|
|
61
61
|
valid_polarity_in_props(Props),
|
|
62
|
+
valid_claim_provenance(Props),
|
|
62
63
|
( memberchk(fact_kind=RawKind, Props) -> validate_fact_shape(RawKind, Props) ; true ).
|
|
63
64
|
validate_entity_shape(test, Props) :-
|
|
64
65
|
!,
|
|
65
66
|
valid_optional_test_enums(Props),
|
|
66
67
|
forall(member(Key=_, Props), \+ is_fact_only_field(Key)).
|
|
68
|
+
validate_entity_shape(req, Props) :-
|
|
69
|
+
!,
|
|
70
|
+
forall(member(Key=_, Props), \+ is_fact_only_field(Key)),
|
|
71
|
+
forall(member(Key=_, Props), \+ is_test_only_field(Key)).
|
|
67
72
|
validate_entity_shape(Type, Props) :-
|
|
68
73
|
Type \= fact,
|
|
69
74
|
Type \= test,
|
|
75
|
+
Type \= req,
|
|
70
76
|
% Non-fact/non-test entities cannot have type-specific fields
|
|
71
77
|
forall(member(Key=_, Props), \+ is_fact_only_field(Key)),
|
|
72
|
-
forall(member(Key=_, Props), \+ is_test_only_field(Key))
|
|
78
|
+
forall(member(Key=_, Props), \+ is_test_only_field(Key)),
|
|
79
|
+
forall(member(Key=_, Props), Key \= logic_claims).
|
|
73
80
|
|
|
74
81
|
% is_fact_only_field(+Key) - true if Key is a fact-specific field
|
|
75
82
|
is_fact_only_field(fact_kind).
|
|
@@ -88,6 +95,8 @@ is_fact_only_field(closed_world).
|
|
|
88
95
|
is_fact_only_field(valid_from).
|
|
89
96
|
is_fact_only_field(valid_to).
|
|
90
97
|
is_fact_only_field(canonical_key).
|
|
98
|
+
is_fact_only_field(claim_key).
|
|
99
|
+
is_fact_only_field(claim_text).
|
|
91
100
|
is_fact_only_field(predicate_name).
|
|
92
101
|
is_fact_only_field(predicate_namespace).
|
|
93
102
|
is_fact_only_field(predicate_arity).
|
|
@@ -172,6 +181,18 @@ valid_strict_polarity_in_props(Props) :-
|
|
|
172
181
|
valid_predicate_polarity_in_props(Props) :-
|
|
173
182
|
( memberchk(polarity=P, Props) -> valid_predicate_polarity(P) ; true ).
|
|
174
183
|
|
|
184
|
+
% claim_key and claim_text form one auditable provenance pair.
|
|
185
|
+
valid_claim_provenance(Props) :-
|
|
186
|
+
( memberchk(claim_key=Key, Props)
|
|
187
|
+
-> memberchk(claim_text=Text, Props),
|
|
188
|
+
nonempty_claim_value(Key),
|
|
189
|
+
nonempty_claim_value(Text)
|
|
190
|
+
; \+ memberchk(claim_text=_, Props)
|
|
191
|
+
).
|
|
192
|
+
|
|
193
|
+
nonempty_claim_value(Value) :- string(Value), Value \= "".
|
|
194
|
+
nonempty_claim_value(Value) :- atom(Value), Value \= ''.
|
|
195
|
+
|
|
175
196
|
% valid_optional_fact_enums(+Props)
|
|
176
197
|
% Validates enum-typed fact fields whenever they are present
|
|
177
198
|
valid_optional_fact_enums(Props) :-
|
package/src/checks.pl
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
19
19
|
check_strict_readiness/1, % Returns list of strict readiness audit violations
|
|
20
20
|
check_predicate_verifiability/1,% Returns list of unverifiable predicate-lane requirement links
|
|
21
|
+
check_logic_coverage/1, % Returns list of incomplete declared logical claim manifests
|
|
21
22
|
run_checks_json/0, % Entry point for JSON output
|
|
22
23
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
23
24
|
]).
|
|
@@ -54,6 +55,7 @@ check_all(ViolationsDict) :-
|
|
|
54
55
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
55
56
|
check_strict_readiness(StrictReadiness),
|
|
56
57
|
check_predicate_verifiability(PredicateVerifiability),
|
|
58
|
+
check_logic_coverage(LogicCoverage),
|
|
57
59
|
ViolationsDict = _{
|
|
58
60
|
must_priority_coverage: MustPriority,
|
|
59
61
|
symbol_coverage: SymbolCoverage,
|
|
@@ -66,7 +68,8 @@ check_all(ViolationsDict) :-
|
|
|
66
68
|
strict_fact_shape: StrictFactShape,
|
|
67
69
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
68
70
|
strict_readiness: StrictReadiness,
|
|
69
|
-
predicate_verifiability: PredicateVerifiability
|
|
71
|
+
predicate_verifiability: PredicateVerifiability,
|
|
72
|
+
logic_coverage: LogicCoverage
|
|
70
73
|
}.
|
|
71
74
|
|
|
72
75
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -800,6 +803,133 @@ predicate_verifiability_kind_label(legacy, "a legacy fact without fact_kind").
|
|
|
800
803
|
predicate_verifiability_kind_label(Kind, Label) :-
|
|
801
804
|
format(string(Label), "fact_kind=~w", [Kind]).
|
|
802
805
|
|
|
806
|
+
%% check_logic_coverage(-Violations)
|
|
807
|
+
% Validates explicitly declared atomic requirement claim manifests. Legacy
|
|
808
|
+
% requirements without logic_claims remain eligible for gradual backfill; once
|
|
809
|
+
% a manifest exists, every key must be grounded by a linked strict-property or
|
|
810
|
+
% predicate fact carrying the same claim_key.
|
|
811
|
+
check_logic_coverage(Violations) :-
|
|
812
|
+
findall(Violation, logic_coverage_violation(Violation), Violations0),
|
|
813
|
+
sort(Violations0, Violations).
|
|
814
|
+
|
|
815
|
+
logic_coverage_violation(violation(
|
|
816
|
+
'logic-coverage',
|
|
817
|
+
ReqId,
|
|
818
|
+
Description,
|
|
819
|
+
"Ground every declared claim with a property_value fact via requires_property or a predicate fact via requires_predicate, preserving the same claim_key",
|
|
820
|
+
Source
|
|
821
|
+
)) :-
|
|
822
|
+
kb:current_req(ReqId),
|
|
823
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
824
|
+
member(ClaimKey, ClaimKeys),
|
|
825
|
+
\+ grounded_requirement_claim(ReqId, ClaimKey),
|
|
826
|
+
format(
|
|
827
|
+
string(Description),
|
|
828
|
+
"Requirement declares logical claim ~w but has no matching ground fact",
|
|
829
|
+
[ClaimKey]
|
|
830
|
+
),
|
|
831
|
+
violation_source(ReqId, req, Source).
|
|
832
|
+
|
|
833
|
+
logic_coverage_violation(violation(
|
|
834
|
+
'logic-coverage',
|
|
835
|
+
ReqId,
|
|
836
|
+
Description,
|
|
837
|
+
"Keep a one-to-one mapping between each atomic claim key and its linked ground fact; split compound clauses before grounding",
|
|
838
|
+
Source
|
|
839
|
+
)) :-
|
|
840
|
+
kb:current_req(ReqId),
|
|
841
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactA),
|
|
842
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactB),
|
|
843
|
+
FactA @< FactB,
|
|
844
|
+
format(
|
|
845
|
+
string(Description),
|
|
846
|
+
"Requirement grounds logical claim ~w more than once through ~w and ~w",
|
|
847
|
+
[ClaimKey, FactA, FactB]
|
|
848
|
+
),
|
|
849
|
+
violation_source(ReqId, req, Source).
|
|
850
|
+
|
|
851
|
+
logic_coverage_violation(violation(
|
|
852
|
+
'logic-coverage',
|
|
853
|
+
ReqId,
|
|
854
|
+
Description,
|
|
855
|
+
"Remove punctuation or wording variants from the atomic inventory and keep one claim key for each distinct logical term",
|
|
856
|
+
Source
|
|
857
|
+
)) :-
|
|
858
|
+
kb:current_req(ReqId),
|
|
859
|
+
grounded_requirement_claim_fact(ReqId, ClaimA, FactA),
|
|
860
|
+
grounded_requirement_claim_fact(ReqId, ClaimB, FactB),
|
|
861
|
+
ClaimA \= ClaimB,
|
|
862
|
+
FactA @< FactB,
|
|
863
|
+
logical_ground_signature(FactA, Signature),
|
|
864
|
+
logical_ground_signature(FactB, Signature),
|
|
865
|
+
format(
|
|
866
|
+
string(Description),
|
|
867
|
+
"Requirement declares duplicate logical ground term ~w through claim keys ~w and ~w",
|
|
868
|
+
[Signature, ClaimA, ClaimB]
|
|
869
|
+
),
|
|
870
|
+
violation_source(ReqId, req, Source).
|
|
871
|
+
|
|
872
|
+
logic_coverage_violation(violation(
|
|
873
|
+
'logic-coverage',
|
|
874
|
+
ReqId,
|
|
875
|
+
Description,
|
|
876
|
+
"Append the linked fact claim_key to the requirement logic_claims manifest, or remove the stale logical link",
|
|
877
|
+
Source
|
|
878
|
+
)) :-
|
|
879
|
+
kb:current_req(ReqId),
|
|
880
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
881
|
+
grounded_requirement_claim(ReqId, ClaimKey),
|
|
882
|
+
\+ memberchk(ClaimKey, ClaimKeys),
|
|
883
|
+
format(
|
|
884
|
+
string(Description),
|
|
885
|
+
"Requirement links ground logical claim ~w but omits it from logic_claims",
|
|
886
|
+
[ClaimKey]
|
|
887
|
+
),
|
|
888
|
+
violation_source(ReqId, req, Source).
|
|
889
|
+
|
|
890
|
+
requirement_logic_claims(ReqId, ClaimKeys) :-
|
|
891
|
+
kb_entity(ReqId, req, Props),
|
|
892
|
+
memberchk(logic_claims=RawClaimKeys, Props),
|
|
893
|
+
kb:normalize_term_atom_list(RawClaimKeys, ClaimKeys).
|
|
894
|
+
|
|
895
|
+
grounded_requirement_claim(ReqId, ClaimKey) :-
|
|
896
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, _FactId).
|
|
897
|
+
|
|
898
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
899
|
+
kb_relationship(requires_property, ReqId, FactId),
|
|
900
|
+
ground_fact_claim_key(FactId, property_value, ClaimKey).
|
|
901
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
902
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
903
|
+
ground_fact_claim_key(FactId, predicate, ClaimKey).
|
|
904
|
+
|
|
905
|
+
logical_ground_signature(FactId, predicate(Namespace, Name, Args, Polarity)) :-
|
|
906
|
+
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
907
|
+
logical_ground_signature(
|
|
908
|
+
FactId,
|
|
909
|
+
property(Subject, Property, Operator, ValueType, Value, Unit, Scope, Polarity)
|
|
910
|
+
) :-
|
|
911
|
+
kb:fact_property_tuple(
|
|
912
|
+
FactId,
|
|
913
|
+
Subject,
|
|
914
|
+
Property,
|
|
915
|
+
Operator,
|
|
916
|
+
ValueType,
|
|
917
|
+
Value,
|
|
918
|
+
Unit,
|
|
919
|
+
Scope,
|
|
920
|
+
Polarity
|
|
921
|
+
).
|
|
922
|
+
|
|
923
|
+
ground_fact_claim_key(FactId, ExpectedKind, ClaimKey) :-
|
|
924
|
+
kb_entity(FactId, fact, Props),
|
|
925
|
+
memberchk(fact_kind=RawKind, Props),
|
|
926
|
+
normalize_term_atom(RawKind, ExpectedKind),
|
|
927
|
+
memberchk(claim_key=RawClaimKey, Props),
|
|
928
|
+
normalize_term_atom(RawClaimKey, ClaimKey),
|
|
929
|
+
memberchk(claim_text=RawClaimText, Props),
|
|
930
|
+
normalize_term_atom(RawClaimText, ClaimText),
|
|
931
|
+
ClaimText \= ''.
|
|
932
|
+
|
|
803
933
|
strict_readiness_has_fact_link(ReqId) :-
|
|
804
934
|
kb_relationship(constrains, ReqId, FactId),
|
|
805
935
|
kb_entity(FactId, fact, _),
|
|
@@ -873,6 +1003,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
873
1003
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
874
1004
|
check_strict_readiness(StrictReadiness),
|
|
875
1005
|
check_predicate_verifiability(PredicateVerifiability),
|
|
1006
|
+
check_logic_coverage(LogicCoverage),
|
|
876
1007
|
ViolationsDict = _{
|
|
877
1008
|
must_priority_coverage: MustPriority,
|
|
878
1009
|
symbol_coverage: SymbolCoverage,
|
|
@@ -885,7 +1016,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
885
1016
|
strict_fact_shape: StrictFactShape,
|
|
886
1017
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
887
1018
|
strict_readiness: StrictReadiness,
|
|
888
|
-
predicate_verifiability: PredicateVerifiability
|
|
1019
|
+
predicate_verifiability: PredicateVerifiability,
|
|
1020
|
+
logic_coverage: LogicCoverage
|
|
889
1021
|
}.
|
|
890
1022
|
|
|
891
1023
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/kb.pl
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
contradicting_reqs/3,
|
|
46
46
|
check_req_contradiction/1,
|
|
47
47
|
normalize_term_atom/2,
|
|
48
|
+
normalize_term_atom_list/2,
|
|
48
49
|
changeset/4, % Export for testing
|
|
49
50
|
kb_uri/1
|
|
50
51
|
]).
|
|
@@ -636,7 +637,7 @@ value_to_literal(Key, Value, Literal) :-
|
|
|
636
637
|
; string(Value)
|
|
637
638
|
-> Literal = Value^^'http://www.w3.org/2001/XMLSchema#string'
|
|
638
639
|
; is_list(Value)
|
|
639
|
-
->
|
|
640
|
+
-> with_output_to(atom(ListStr), write_term(Value, [quoted(true)])),
|
|
640
641
|
Literal = ListStr^^'http://www.w3.org/2001/XMLSchema#string'
|
|
641
642
|
; format(atom(Str), '~w', [Value]),
|
|
642
643
|
Literal = Str^^'http://www.w3.org/2001/XMLSchema#string'
|
|
@@ -1005,6 +1006,26 @@ req_conflict(ReqA, ReqB, Reason) :-
|
|
|
1005
1006
|
; property_conflict(SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, PolarityA,
|
|
1006
1007
|
OpB, ValTypeB, ValB, UnitB, PolarityB, Reason)
|
|
1007
1008
|
).
|
|
1009
|
+
req_conflict(ReqA, ReqB, Reason) :-
|
|
1010
|
+
effective_req_predicate(ReqA, FactA, Namespace, Name, Args, PolarityA),
|
|
1011
|
+
effective_req_predicate(ReqB, FactB, Namespace, Name, Args, PolarityB),
|
|
1012
|
+
FactA \= FactB,
|
|
1013
|
+
opposite_predicate_polarity(PolarityA, PolarityB),
|
|
1014
|
+
atomic_list_concat(Args, ',', ArgsText),
|
|
1015
|
+
format(
|
|
1016
|
+
string(Reason),
|
|
1017
|
+
"Predicate conflict on ~w:~w(~w): ~w asserts ~w while ~w asserts ~w",
|
|
1018
|
+
[Namespace, Name, ArgsText, ReqA, PolarityA, ReqB, PolarityB]
|
|
1019
|
+
).
|
|
1020
|
+
|
|
1021
|
+
%% effective_req_predicate(+ReqId, -FactId, -Namespace, -Name, -Args, -Polarity)
|
|
1022
|
+
% Ground predicate claim required by a requirement.
|
|
1023
|
+
effective_req_predicate(ReqId, FactId, Namespace, Name, Args, Polarity) :-
|
|
1024
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
1025
|
+
predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
1026
|
+
|
|
1027
|
+
opposite_predicate_polarity(assert, deny).
|
|
1028
|
+
opposite_predicate_polarity(deny, assert).
|
|
1008
1029
|
|
|
1009
1030
|
%% fact_subject_key(+FactId, -SubjectKey)
|
|
1010
1031
|
% Extract the normalized subject key for strict subject facts.
|
package/src/sparql_client.pl
CHANGED
|
@@ -44,7 +44,10 @@ validate_remote_endpoint(Endpoint) :-
|
|
|
44
44
|
|
|
45
45
|
validate_select_query(Query) :-
|
|
46
46
|
normalize_space(atom(Normalized), Query),
|
|
47
|
-
|
|
47
|
+
split_string(Normalized, "\n", " \t\r", RawLines),
|
|
48
|
+
strip_sparql_prelude(RawLines, RemainingLines),
|
|
49
|
+
atomic_list_concat(RemainingLines, ' ', Remainder),
|
|
50
|
+
downcase_atom(Remainder, Lower),
|
|
48
51
|
sub_atom(Lower, 0, 6, After, 'select'),
|
|
49
52
|
( After =:= 0
|
|
50
53
|
; sub_atom(Lower, 6, 1, _, ' ')
|
|
@@ -54,6 +57,25 @@ validate_select_query(Query) :-
|
|
|
54
57
|
validate_select_query(Query) :-
|
|
55
58
|
throw(error(domain_error(sparql_select_query, Query), _)).
|
|
56
59
|
|
|
60
|
+
strip_sparql_prelude([], []).
|
|
61
|
+
strip_sparql_prelude([Line | Rest], Result) :-
|
|
62
|
+
normalize_space(atom(Trimmed), Line),
|
|
63
|
+
( Trimmed == ''
|
|
64
|
+
-> strip_sparql_prelude(Rest, Result)
|
|
65
|
+
; is_prelude_line(Trimmed)
|
|
66
|
+
-> strip_sparql_prelude(Rest, Result)
|
|
67
|
+
; Result = [Trimmed | Rest]
|
|
68
|
+
).
|
|
69
|
+
|
|
70
|
+
is_prelude_line(Line) :-
|
|
71
|
+
downcase_atom(Line, Lower),
|
|
72
|
+
split_string(Lower, " ", " \t", Tokens0),
|
|
73
|
+
exclude(=("") , Tokens0, Tokens),
|
|
74
|
+
Tokens = [FirstToken | _],
|
|
75
|
+
( FirstToken == "prefix"
|
|
76
|
+
; FirstToken == "base"
|
|
77
|
+
).
|
|
78
|
+
|
|
57
79
|
endpoint_host(Endpoint, Host) :-
|
|
58
80
|
strip_endpoint_scheme(Endpoint, WithoutScheme),
|
|
59
81
|
segment_before(WithoutScheme, '/', WithoutPath),
|
package/src/status.pl
CHANGED
|
@@ -117,6 +117,7 @@ documentation_markdown_untracked(WorkspaceRoot) :-
|
|
|
117
117
|
documentation_markdown_file(Path, Path) :-
|
|
118
118
|
exists_file(Path),
|
|
119
119
|
file_name_extension(_, md, Path),
|
|
120
|
+
entity_documentation_file(Path),
|
|
120
121
|
\+ ignored_documentation_file(Path),
|
|
121
122
|
!.
|
|
122
123
|
documentation_markdown_file(Path, FilePath) :-
|
|
@@ -140,6 +141,7 @@ known_source_path(RelativePath) :-
|
|
|
140
141
|
|
|
141
142
|
directory_tree_newer(Path, SnapshotTime) :-
|
|
142
143
|
exists_file(Path),
|
|
144
|
+
entity_documentation_file(Path),
|
|
143
145
|
\+ ignored_documentation_file(Path),
|
|
144
146
|
time_file(Path, EntryTime),
|
|
145
147
|
EntryTime > SnapshotTime,
|
|
@@ -218,5 +220,12 @@ source_value_atom(Val, Atom) :-
|
|
|
218
220
|
ignored_documentation_file(Path) :-
|
|
219
221
|
file_base_name(Path, 'README.md').
|
|
220
222
|
|
|
223
|
+
entity_documentation_file(Path) :-
|
|
224
|
+
read_file_to_string(Path, Content, []),
|
|
225
|
+
sub_string(Content, 0, 3, _, "---"),
|
|
226
|
+
sub_string(Content, _, _, _, "id:"),
|
|
227
|
+
sub_string(Content, _, _, _, "title:"),
|
|
228
|
+
sub_string(Content, _, _, _, "status:").
|
|
229
|
+
|
|
221
230
|
dict_json_string(Dict, JsonString) :-
|
|
222
231
|
with_output_to(string(JsonString), json_write_dict(current_output, Dict, [])).
|