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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.1.10",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -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).
@@ -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(_Type, Prop, Value) :-
37
- % find declared property type, default to atom
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)