kibi-core 0.1.10 → 0.3.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 +18 -0
- package/schema/validation.pl +114 -4
- package/src/checks.pl +96 -2
- package/src/discovery.pl +461 -0
- package/src/kb.pl +444 -46
- package/src/status.pl +215 -0
package/package.json
CHANGED
package/schema/entities.pl
CHANGED
|
@@ -29,6 +29,24 @@ entity_property(_, severity, atom).
|
|
|
29
29
|
entity_property(_, links, list).
|
|
30
30
|
entity_property(_, text_ref, uri).
|
|
31
31
|
|
|
32
|
+
% Typed fact fields - only valid for fact entities
|
|
33
|
+
entity_property(fact, fact_kind, atom).
|
|
34
|
+
entity_property(fact, subject_key, string).
|
|
35
|
+
entity_property(fact, property_key, string).
|
|
36
|
+
entity_property(fact, operator, atom).
|
|
37
|
+
entity_property(fact, value_type, atom).
|
|
38
|
+
entity_property(fact, value_string, string).
|
|
39
|
+
entity_property(fact, value_int, integer).
|
|
40
|
+
entity_property(fact, value_number, number).
|
|
41
|
+
entity_property(fact, value_bool, boolean).
|
|
42
|
+
entity_property(fact, unit, string).
|
|
43
|
+
entity_property(fact, scope, string).
|
|
44
|
+
entity_property(fact, polarity, atom).
|
|
45
|
+
entity_property(fact, closed_world, boolean).
|
|
46
|
+
entity_property(fact, valid_from, datetime).
|
|
47
|
+
entity_property(fact, valid_to, datetime).
|
|
48
|
+
entity_property(fact, canonical_key, string).
|
|
49
|
+
|
|
32
50
|
% Required properties for all entity types
|
|
33
51
|
required_property(Type, id) :- entity_type(Type).
|
|
34
52
|
required_property(Type, title) :- entity_type(Type).
|
package/schema/validation.pl
CHANGED
|
@@ -17,7 +17,9 @@ validate_entity(Type, Props) :-
|
|
|
17
17
|
% required properties present
|
|
18
18
|
forall(required_property(Type, P), memberchk(P=_Val, Props)),
|
|
19
19
|
% all properties have correct types
|
|
20
|
-
forall(member(Key=Val, Props), validate_property_type(Type, Key, Val))
|
|
20
|
+
forall(member(Key=Val, Props), validate_property_type(Type, Key, Val)),
|
|
21
|
+
% validate entity-specific shape constraints
|
|
22
|
+
validate_entity_shape(Type, Props).
|
|
21
23
|
|
|
22
24
|
% validate_relationship(+RelType, +From, +To)
|
|
23
25
|
% From and To are pairs Type=Id or structures type(Type) - allow Type or Type=Id
|
|
@@ -33,9 +35,8 @@ type_of(Type, Type) :- atom(Type), entity_type(Type), !.
|
|
|
33
35
|
type_of(Type=_Id, Type) :- atom(Type), entity_type(Type), !.
|
|
34
36
|
|
|
35
37
|
% validate_property_type(+EntityType, +Prop, +Value)
|
|
36
|
-
validate_property_type(
|
|
37
|
-
|
|
38
|
-
( entity_property(_Any, Prop, Kind) -> true ; Kind = atom ),
|
|
38
|
+
validate_property_type(Type, Prop, Value) :-
|
|
39
|
+
entity_property(Type, Prop, Kind),
|
|
39
40
|
check_kind(Kind, Value), !.
|
|
40
41
|
|
|
41
42
|
% check_kind(Kind, Value) succeeds if Value matches Kind
|
|
@@ -44,6 +45,115 @@ check_kind(string, V) :- string(V).
|
|
|
44
45
|
check_kind(datetime, V) :- string(V). % accept ISO strings for now
|
|
45
46
|
check_kind(list, V) :- is_list(V).
|
|
46
47
|
check_kind(uri, V) :- string(V).
|
|
48
|
+
check_kind(integer, V) :- integer(V).
|
|
49
|
+
check_kind(number, V) :- number(V).
|
|
50
|
+
check_kind(boolean, true).
|
|
51
|
+
check_kind(boolean, false).
|
|
47
52
|
|
|
48
53
|
% Fallback false
|
|
49
54
|
check_kind(_, _) :- fail.
|
|
55
|
+
|
|
56
|
+
% validate_entity_shape(+Type, +Props)
|
|
57
|
+
% Validates entity-specific shape constraints (e.g., strict fact shapes)
|
|
58
|
+
validate_entity_shape(fact, Props) :-
|
|
59
|
+
!,
|
|
60
|
+
valid_optional_fact_enums(Props),
|
|
61
|
+
valid_polarity_in_props(Props),
|
|
62
|
+
( memberchk(fact_kind=RawKind, Props) -> validate_fact_shape(RawKind, Props) ; true ).
|
|
63
|
+
validate_entity_shape(Type, Props) :-
|
|
64
|
+
Type \= fact,
|
|
65
|
+
% Non-fact entities cannot have fact-only fields
|
|
66
|
+
forall(member(Key=_Val, Props), \+ is_fact_only_field(Key)).
|
|
67
|
+
|
|
68
|
+
% is_fact_only_field(+Key) - true if Key is a fact-specific field
|
|
69
|
+
is_fact_only_field(fact_kind).
|
|
70
|
+
is_fact_only_field(subject_key).
|
|
71
|
+
is_fact_only_field(property_key).
|
|
72
|
+
is_fact_only_field(operator).
|
|
73
|
+
is_fact_only_field(value_type).
|
|
74
|
+
is_fact_only_field(value_string).
|
|
75
|
+
is_fact_only_field(value_int).
|
|
76
|
+
is_fact_only_field(value_number).
|
|
77
|
+
is_fact_only_field(value_bool).
|
|
78
|
+
is_fact_only_field(unit).
|
|
79
|
+
is_fact_only_field(scope).
|
|
80
|
+
is_fact_only_field(polarity).
|
|
81
|
+
is_fact_only_field(closed_world).
|
|
82
|
+
is_fact_only_field(valid_from).
|
|
83
|
+
is_fact_only_field(valid_to).
|
|
84
|
+
is_fact_only_field(canonical_key).
|
|
85
|
+
|
|
86
|
+
% validate_fact_shape(+Kind, +Props)
|
|
87
|
+
validate_fact_shape(subject, Props) :-
|
|
88
|
+
memberchk(subject_key=_Val, Props),
|
|
89
|
+
valid_optional_fact_enums(Props),
|
|
90
|
+
valid_polarity_in_props(Props).
|
|
91
|
+
validate_fact_shape(property_value, Props) :-
|
|
92
|
+
memberchk(subject_key=_Subject, Props),
|
|
93
|
+
memberchk(property_key=_Property, Props),
|
|
94
|
+
memberchk(operator=Op, Props),
|
|
95
|
+
valid_operator(Op),
|
|
96
|
+
memberchk(value_type=VT, Props),
|
|
97
|
+
valid_value_type(VT),
|
|
98
|
+
exactly_one_value_field(Props),
|
|
99
|
+
value_type_matches_field(VT, Props),
|
|
100
|
+
valid_optional_fact_enums(Props),
|
|
101
|
+
valid_polarity_in_props(Props).
|
|
102
|
+
validate_fact_shape(observation, Props) :-
|
|
103
|
+
valid_optional_fact_enums(Props),
|
|
104
|
+
valid_polarity_in_props(Props).
|
|
105
|
+
validate_fact_shape(meta, Props) :-
|
|
106
|
+
% Meta facts are allowed but don't require full strict property tuple yet
|
|
107
|
+
valid_optional_fact_enums(Props),
|
|
108
|
+
valid_polarity_in_props(Props).
|
|
109
|
+
validate_fact_shape(Kind, _Props) :-
|
|
110
|
+
% Unknown fact_kind values fail validation
|
|
111
|
+
\+ memberchk(Kind, [subject, property_value, observation, meta]),
|
|
112
|
+
fail.
|
|
113
|
+
|
|
114
|
+
% valid_operator(+Op)
|
|
115
|
+
valid_operator(eq).
|
|
116
|
+
valid_operator(neq).
|
|
117
|
+
valid_operator(lt).
|
|
118
|
+
valid_operator(lte).
|
|
119
|
+
valid_operator(gt).
|
|
120
|
+
valid_operator(gte).
|
|
121
|
+
|
|
122
|
+
% valid_value_type(+VT)
|
|
123
|
+
valid_value_type(string).
|
|
124
|
+
valid_value_type(int).
|
|
125
|
+
valid_value_type(number).
|
|
126
|
+
valid_value_type(bool).
|
|
127
|
+
|
|
128
|
+
% valid_polarity_in_props(+Props)
|
|
129
|
+
% Validates polarity if present; succeeds if no polarity in props
|
|
130
|
+
valid_polarity_in_props(Props) :-
|
|
131
|
+
( memberchk(polarity=P, Props) -> valid_polarity(P) ; true ).
|
|
132
|
+
|
|
133
|
+
% valid_optional_fact_enums(+Props)
|
|
134
|
+
% Validates enum-typed fact fields whenever they are present
|
|
135
|
+
valid_optional_fact_enums(Props) :-
|
|
136
|
+
( memberchk(operator=Op, Props) -> valid_operator(Op) ; true ),
|
|
137
|
+
( memberchk(value_type=VT, Props) -> valid_value_type(VT) ; true ).
|
|
138
|
+
|
|
139
|
+
% valid_polarity(+P)
|
|
140
|
+
valid_polarity(require).
|
|
141
|
+
valid_polarity(forbid).
|
|
142
|
+
|
|
143
|
+
% exactly_one_value_field(+Props)
|
|
144
|
+
exactly_one_value_field(Props) :-
|
|
145
|
+
findall(F, (member(F=_, Props), is_value_field(F)), Fields),
|
|
146
|
+
length(Fields, 1).
|
|
147
|
+
|
|
148
|
+
% is_value_field(+Field)
|
|
149
|
+
is_value_field(value_string).
|
|
150
|
+
is_value_field(value_int).
|
|
151
|
+
is_value_field(value_number).
|
|
152
|
+
is_value_field(value_bool).
|
|
153
|
+
|
|
154
|
+
% value_type_matches_field(+ValueType, +Props)
|
|
155
|
+
% Ensures that value_type matches the actual value field present
|
|
156
|
+
value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
|
|
157
|
+
value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
158
|
+
value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
|
|
159
|
+
value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
|
package/src/checks.pl
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
check_required_fields/1, % Returns list of missing required field violations
|
|
15
15
|
check_deprecated_adrs/1, % Returns list of deprecated ADR violations
|
|
16
16
|
check_domain_contradictions/1, % Returns list of contradiction violations
|
|
17
|
+
check_strict_fact_shape/1, % Returns list of malformed strict fact violations
|
|
17
18
|
run_checks_json/0 % Entry point for JSON output
|
|
18
19
|
]).
|
|
19
20
|
|
|
@@ -45,6 +46,7 @@ check_all(ViolationsDict) :-
|
|
|
45
46
|
check_required_fields(RequiredFields),
|
|
46
47
|
check_deprecated_adrs(DeprecatedADRs),
|
|
47
48
|
check_domain_contradictions(Contradictions),
|
|
49
|
+
check_strict_fact_shape(StrictFactShape),
|
|
48
50
|
ViolationsDict = _{
|
|
49
51
|
must_priority_coverage: MustPriority,
|
|
50
52
|
symbol_coverage: SymbolCoverage,
|
|
@@ -53,7 +55,8 @@ check_all(ViolationsDict) :-
|
|
|
53
55
|
no_cycles: Cycles,
|
|
54
56
|
required_fields: RequiredFields,
|
|
55
57
|
deprecated_adr_no_successor: DeprecatedADRs,
|
|
56
|
-
domain_contradictions: Contradictions
|
|
58
|
+
domain_contradictions: Contradictions,
|
|
59
|
+
strict_fact_shape: StrictFactShape
|
|
57
60
|
}.
|
|
58
61
|
|
|
59
62
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -334,6 +337,95 @@ deprecated_adr_violation(violation(
|
|
|
334
337
|
; Source = ""
|
|
335
338
|
).
|
|
336
339
|
|
|
340
|
+
%% check_strict_fact_shape(-Violations)
|
|
341
|
+
% Finds all strict facts (with fact_kind) that have malformed shape.
|
|
342
|
+
% Only checks facts with fact_kind present; legacy facts without fact_kind are ignored.
|
|
343
|
+
% implements REQ-006
|
|
344
|
+
check_strict_fact_shape(Violations) :-
|
|
345
|
+
findall(
|
|
346
|
+
Violation,
|
|
347
|
+
strict_fact_shape_violation(Violation),
|
|
348
|
+
Violations0
|
|
349
|
+
),
|
|
350
|
+
sort(Violations0, Violations).
|
|
351
|
+
|
|
352
|
+
strict_fact_shape_violation(violation(
|
|
353
|
+
'strict-fact-shape',
|
|
354
|
+
FactId,
|
|
355
|
+
Description,
|
|
356
|
+
Suggestion,
|
|
357
|
+
Source
|
|
358
|
+
)) :-
|
|
359
|
+
kb_entity(FactId, fact, Props),
|
|
360
|
+
memberchk(fact_kind=RawKind, Props), % Only check facts with fact_kind
|
|
361
|
+
normalize_term_atom(RawKind, Kind), % Handle typed literals like ^^(subject, xsd:string)
|
|
362
|
+
|
|
363
|
+
% Check for malformed shape based on fact kind
|
|
364
|
+
( Kind = subject
|
|
365
|
+
-> ( memberchk(subject_key=_, Props)
|
|
366
|
+
-> fail % Well-formed, no violation
|
|
367
|
+
; Description = "Subject fact missing required field: subject_key",
|
|
368
|
+
Suggestion = "Add subject_key to define the subject domain key"
|
|
369
|
+
)
|
|
370
|
+
; Kind = property_value
|
|
371
|
+
-> findall(Msg, property_value_shape_error(Props, Msg), Errors),
|
|
372
|
+
( Errors = []
|
|
373
|
+
-> fail % Well-formed, no violation
|
|
374
|
+
; Errors = [First|_],
|
|
375
|
+
Description = First,
|
|
376
|
+
Suggestion = "Ensure property_value facts have subject_key, property_key, operator, value_type, and exactly one value field"
|
|
377
|
+
)
|
|
378
|
+
; Kind = observation
|
|
379
|
+
-> fail % Observation facts have no required fields beyond fact_kind
|
|
380
|
+
; Kind = meta
|
|
381
|
+
-> fail % Meta facts have no required fields beyond fact_kind
|
|
382
|
+
; % Unknown fact_kind - report as malformed
|
|
383
|
+
format(string(Description), "Unknown fact_kind: ~w", [Kind]),
|
|
384
|
+
Suggestion = "Use one of: subject, property_value, observation, meta"
|
|
385
|
+
),
|
|
386
|
+
|
|
387
|
+
( memberchk(source=Source0, Props)
|
|
388
|
+
-> normalize_term_atom(Source0, Source)
|
|
389
|
+
; Source = ""
|
|
390
|
+
).
|
|
391
|
+
|
|
392
|
+
% property_value_shape_error(+Props, -ErrorMsg)
|
|
393
|
+
% Returns an error message if the property_value fact has a shape error.
|
|
394
|
+
property_value_shape_error(Props, "Property value fact missing required field: subject_key") :-
|
|
395
|
+
\+ memberchk(subject_key=_, Props).
|
|
396
|
+
property_value_shape_error(Props, "Property value fact missing required field: property_key") :-
|
|
397
|
+
memberchk(subject_key=_, Props),
|
|
398
|
+
\+ memberchk(property_key=_, Props).
|
|
399
|
+
property_value_shape_error(Props, "Property value fact missing required field: operator") :-
|
|
400
|
+
memberchk(property_key=_, Props),
|
|
401
|
+
\+ memberchk(operator=_, Props).
|
|
402
|
+
property_value_shape_error(Props, "Property value fact missing required field: value_type") :-
|
|
403
|
+
memberchk(operator=_, Props),
|
|
404
|
+
\+ memberchk(value_type=_, Props).
|
|
405
|
+
property_value_shape_error(Props, "Property value fact missing value field (value_string, value_int, value_number, or value_bool)") :-
|
|
406
|
+
memberchk(value_type=_, Props),
|
|
407
|
+
\+ (memberchk(value_string=_, Props); memberchk(value_int=_, Props);
|
|
408
|
+
memberchk(value_number=_, Props); memberchk(value_bool=_, Props)).
|
|
409
|
+
property_value_shape_error(Props, "Property value fact has multiple value fields (should have exactly one)") :-
|
|
410
|
+
findall(F, (member(F=_, Props), is_value_field(F)), Fields),
|
|
411
|
+
length(Fields, Count),
|
|
412
|
+
Count > 1.
|
|
413
|
+
property_value_shape_error(Props, "Property value fact value_type does not match value field") :-
|
|
414
|
+
memberchk(value_type=VT, Props),
|
|
415
|
+
\+ value_type_matches_field(VT, Props).
|
|
416
|
+
|
|
417
|
+
% is_value_field(+Field)
|
|
418
|
+
is_value_field(value_string).
|
|
419
|
+
is_value_field(value_int).
|
|
420
|
+
is_value_field(value_number).
|
|
421
|
+
is_value_field(value_bool).
|
|
422
|
+
|
|
423
|
+
% value_type_matches_field(+ValueType, +Props)
|
|
424
|
+
value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
|
|
425
|
+
value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
426
|
+
value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
|
|
427
|
+
value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
|
|
428
|
+
|
|
337
429
|
%% check_domain_contradictions(-Violations)
|
|
338
430
|
% Finds all pairs of requirements with contradicting required properties.
|
|
339
431
|
check_domain_contradictions(Violations) :-
|
|
@@ -398,6 +490,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
398
490
|
check_required_fields(RequiredFields),
|
|
399
491
|
check_deprecated_adrs(DeprecatedADRs),
|
|
400
492
|
check_domain_contradictions(Contradictions),
|
|
493
|
+
check_strict_fact_shape(StrictFactShape),
|
|
401
494
|
ViolationsDict = _{
|
|
402
495
|
must_priority_coverage: MustPriority,
|
|
403
496
|
symbol_coverage: SymbolCoverage,
|
|
@@ -406,7 +499,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
406
499
|
no_cycles: Cycles,
|
|
407
500
|
required_fields: RequiredFields,
|
|
408
501
|
deprecated_adr_no_successor: DeprecatedADRs,
|
|
409
|
-
domain_contradictions: Contradictions
|
|
502
|
+
domain_contradictions: Contradictions,
|
|
503
|
+
strict_fact_shape: StrictFactShape
|
|
410
504
|
}.
|
|
411
505
|
|
|
412
506
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|