kibi-core 0.5.0 → 0.5.2
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 +5 -2
- package/src/checks.pl +159 -6
- package/src/kb.pl +52 -8
- package/src/status.pl +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Core Prolog modules and RDF graph logic for Kibi",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"bun": ">=1.0"
|
|
10
10
|
},
|
|
11
11
|
"main": "./src/kb.pl",
|
|
12
|
-
"files": [
|
|
12
|
+
"files": [
|
|
13
|
+
"src/**/*.pl",
|
|
14
|
+
"schema/**/*.pl"
|
|
15
|
+
],
|
|
13
16
|
"license": "AGPL-3.0-or-later",
|
|
14
17
|
"author": "Piotr Franczyk",
|
|
15
18
|
"repository": {
|
package/src/checks.pl
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
check_deprecated_adrs/1, % Returns list of deprecated ADR violations
|
|
16
16
|
check_domain_contradictions/1, % Returns list of contradiction violations
|
|
17
17
|
check_strict_fact_shape/1, % Returns list of malformed strict fact violations
|
|
18
|
-
|
|
18
|
+
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
19
|
+
run_checks_json/0, % Entry point for JSON output
|
|
20
|
+
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
19
21
|
]).
|
|
20
22
|
|
|
21
23
|
:- use_module(library(http/json)).
|
|
@@ -47,6 +49,7 @@ check_all(ViolationsDict) :-
|
|
|
47
49
|
check_deprecated_adrs(DeprecatedADRs),
|
|
48
50
|
check_domain_contradictions(Contradictions),
|
|
49
51
|
check_strict_fact_shape(StrictFactShape),
|
|
52
|
+
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
50
53
|
ViolationsDict = _{
|
|
51
54
|
must_priority_coverage: MustPriority,
|
|
52
55
|
symbol_coverage: SymbolCoverage,
|
|
@@ -56,7 +59,8 @@ check_all(ViolationsDict) :-
|
|
|
56
59
|
required_fields: RequiredFields,
|
|
57
60
|
deprecated_adr_no_successor: DeprecatedADRs,
|
|
58
61
|
domain_contradictions: Contradictions,
|
|
59
|
-
strict_fact_shape: StrictFactShape
|
|
62
|
+
strict_fact_shape: StrictFactShape,
|
|
63
|
+
strict_req_fact_pairing: StrictReqFactPairing
|
|
60
64
|
}.
|
|
61
65
|
|
|
62
66
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -412,7 +416,8 @@ property_value_shape_error(Props, "Property value fact has multiple value fields
|
|
|
412
416
|
length(Fields, Count),
|
|
413
417
|
Count > 1.
|
|
414
418
|
property_value_shape_error(Props, "Property value fact value_type does not match value field") :-
|
|
415
|
-
memberchk(value_type=
|
|
419
|
+
memberchk(value_type=RawVT, Props),
|
|
420
|
+
normalize_term_atom(RawVT, VT),
|
|
416
421
|
\+ value_type_matches_field(VT, Props).
|
|
417
422
|
|
|
418
423
|
% is_value_field(+Field)
|
|
@@ -445,6 +450,138 @@ check_domain_contradictions(Violations) :-
|
|
|
445
450
|
Violations
|
|
446
451
|
).
|
|
447
452
|
|
|
453
|
+
%% check_strict_req_fact_pairing(-Violations)
|
|
454
|
+
% Finds current requirements attempting strict-lane modeling with incomplete
|
|
455
|
+
% subject/property pairing or wrong-lane fact targets.
|
|
456
|
+
check_strict_req_fact_pairing(Violations) :-
|
|
457
|
+
findall(
|
|
458
|
+
Violation,
|
|
459
|
+
strict_req_fact_pairing_violation(Violation),
|
|
460
|
+
Violations0
|
|
461
|
+
),
|
|
462
|
+
sort(Violations0, Violations).
|
|
463
|
+
|
|
464
|
+
strict_req_fact_pairing_violation(violation(
|
|
465
|
+
'strict-req-fact-pairing',
|
|
466
|
+
ReqId,
|
|
467
|
+
Description,
|
|
468
|
+
Suggestion,
|
|
469
|
+
Source
|
|
470
|
+
)) :-
|
|
471
|
+
strict_req_fact_pairing_issue(ReqId, Description, Suggestion),
|
|
472
|
+
violation_source(ReqId, req, Source).
|
|
473
|
+
|
|
474
|
+
strict_req_fact_pairing_issue(
|
|
475
|
+
ReqId,
|
|
476
|
+
Description,
|
|
477
|
+
"Add a property_value fact via requires_property for the same subject_key"
|
|
478
|
+
) :-
|
|
479
|
+
kb:current_req(ReqId),
|
|
480
|
+
kb_relationship(constrains, ReqId, SubjectFactId),
|
|
481
|
+
strict_req_fact_pairing_fact_kind(SubjectFactId, subject),
|
|
482
|
+
kb:fact_subject_key(SubjectFactId, SubjectKey),
|
|
483
|
+
\+ kb:effective_req_property_fact(
|
|
484
|
+
ReqId,
|
|
485
|
+
SubjectKey,
|
|
486
|
+
_PropertyFactId,
|
|
487
|
+
_PropertyKey,
|
|
488
|
+
_Operator,
|
|
489
|
+
_ValueType,
|
|
490
|
+
_Value,
|
|
491
|
+
_Unit,
|
|
492
|
+
_Scope,
|
|
493
|
+
_Polarity,
|
|
494
|
+
_ValidFrom,
|
|
495
|
+
_ValidTo
|
|
496
|
+
),
|
|
497
|
+
format(
|
|
498
|
+
string(Description),
|
|
499
|
+
"Requirement constrains ~w (~w) but has no matching strict requires_property fact",
|
|
500
|
+
[SubjectFactId, SubjectKey]
|
|
501
|
+
).
|
|
502
|
+
|
|
503
|
+
strict_req_fact_pairing_issue(
|
|
504
|
+
ReqId,
|
|
505
|
+
Description,
|
|
506
|
+
"Add a subject fact via constrains for the same subject_key or remove the mismatched requires_property link"
|
|
507
|
+
) :-
|
|
508
|
+
kb:current_req(ReqId),
|
|
509
|
+
kb_relationship(requires_property, ReqId, PropertyFactId),
|
|
510
|
+
strict_req_fact_pairing_fact_kind(PropertyFactId, property_value),
|
|
511
|
+
kb:fact_property_tuple(
|
|
512
|
+
PropertyFactId,
|
|
513
|
+
SubjectKey,
|
|
514
|
+
_PropertyKey,
|
|
515
|
+
_Operator,
|
|
516
|
+
_ValueType,
|
|
517
|
+
_Value,
|
|
518
|
+
_Unit,
|
|
519
|
+
_Scope,
|
|
520
|
+
_Polarity
|
|
521
|
+
),
|
|
522
|
+
\+ kb:effective_req_property_fact(
|
|
523
|
+
ReqId,
|
|
524
|
+
SubjectKey,
|
|
525
|
+
PropertyFactId,
|
|
526
|
+
_MatchedPropertyKey,
|
|
527
|
+
_MatchedOperator,
|
|
528
|
+
_MatchedValueType,
|
|
529
|
+
_MatchedValue,
|
|
530
|
+
_MatchedUnit,
|
|
531
|
+
_MatchedScope,
|
|
532
|
+
_MatchedPolarity,
|
|
533
|
+
_MatchedValidFrom,
|
|
534
|
+
_MatchedValidTo
|
|
535
|
+
),
|
|
536
|
+
format(
|
|
537
|
+
string(Description),
|
|
538
|
+
"Requirement requires_property ~w (~w) but has no matching strict subject fact via constrains",
|
|
539
|
+
[PropertyFactId, SubjectKey]
|
|
540
|
+
).
|
|
541
|
+
|
|
542
|
+
strict_req_fact_pairing_issue(
|
|
543
|
+
ReqId,
|
|
544
|
+
Description,
|
|
545
|
+
"Use a subject fact with constrains for contradiction-safe semantics; keep non-subject facts out of strict pairing"
|
|
546
|
+
) :-
|
|
547
|
+
kb:current_req(ReqId),
|
|
548
|
+
kb_relationship(constrains, ReqId, FactId),
|
|
549
|
+
strict_req_fact_pairing_fact_kind(FactId, Kind),
|
|
550
|
+
Kind \= subject,
|
|
551
|
+
strict_req_fact_pairing_kind_label(Kind, KindLabel),
|
|
552
|
+
format(
|
|
553
|
+
string(Description),
|
|
554
|
+
"Requirement links ~w via constrains using ~w; contradiction-safe constrains links must target subject facts",
|
|
555
|
+
[FactId, KindLabel]
|
|
556
|
+
).
|
|
557
|
+
|
|
558
|
+
strict_req_fact_pairing_issue(
|
|
559
|
+
ReqId,
|
|
560
|
+
Description,
|
|
561
|
+
"Use a property_value fact with requires_property for contradiction-safe semantics; keep non-property facts out of strict pairing"
|
|
562
|
+
) :-
|
|
563
|
+
kb:current_req(ReqId),
|
|
564
|
+
kb_relationship(requires_property, ReqId, FactId),
|
|
565
|
+
strict_req_fact_pairing_fact_kind(FactId, Kind),
|
|
566
|
+
Kind \= property_value,
|
|
567
|
+
strict_req_fact_pairing_kind_label(Kind, KindLabel),
|
|
568
|
+
format(
|
|
569
|
+
string(Description),
|
|
570
|
+
"Requirement links ~w via requires_property using ~w; contradiction-safe requires_property links must target property_value facts",
|
|
571
|
+
[FactId, KindLabel]
|
|
572
|
+
).
|
|
573
|
+
|
|
574
|
+
strict_req_fact_pairing_fact_kind(FactId, Kind) :-
|
|
575
|
+
kb_entity(FactId, fact, Props),
|
|
576
|
+
( memberchk(fact_kind=RawKind, Props)
|
|
577
|
+
-> normalize_term_atom(RawKind, Kind)
|
|
578
|
+
; Kind = legacy
|
|
579
|
+
).
|
|
580
|
+
|
|
581
|
+
strict_req_fact_pairing_kind_label(legacy, "a legacy fact without fact_kind").
|
|
582
|
+
strict_req_fact_pairing_kind_label(Kind, Label) :-
|
|
583
|
+
format(string(Label), "a fact_kind=~w fact", [Kind]).
|
|
584
|
+
|
|
448
585
|
%% run_checks_json
|
|
449
586
|
% Entry point for JSON output. Prints all violations as JSON to stdout.
|
|
450
587
|
run_checks_json :-
|
|
@@ -492,6 +629,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
492
629
|
check_deprecated_adrs(DeprecatedADRs),
|
|
493
630
|
check_domain_contradictions(Contradictions),
|
|
494
631
|
check_strict_fact_shape(StrictFactShape),
|
|
632
|
+
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
495
633
|
ViolationsDict = _{
|
|
496
634
|
must_priority_coverage: MustPriority,
|
|
497
635
|
symbol_coverage: SymbolCoverage,
|
|
@@ -501,7 +639,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
501
639
|
required_fields: RequiredFields,
|
|
502
640
|
deprecated_adr_no_successor: DeprecatedADRs,
|
|
503
641
|
domain_contradictions: Contradictions,
|
|
504
|
-
strict_fact_shape: StrictFactShape
|
|
642
|
+
strict_fact_shape: StrictFactShape,
|
|
643
|
+
strict_req_fact_pairing: StrictReqFactPairing
|
|
505
644
|
}.
|
|
506
645
|
|
|
507
646
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
|
@@ -552,8 +691,22 @@ violation_text(Val, Text) :-
|
|
|
552
691
|
term_string(Val, Text).
|
|
553
692
|
|
|
554
693
|
violation_id_text(Val, Text) :-
|
|
555
|
-
|
|
556
|
-
|
|
694
|
+
nonvar(Val),
|
|
695
|
+
Val =.. ['^^', Inner, _Type],
|
|
696
|
+
!,
|
|
697
|
+
violation_id_text(Inner, Text).
|
|
698
|
+
violation_id_text(literal(type(_, Val)), Text) :-
|
|
699
|
+
!,
|
|
700
|
+
violation_id_text(Val, Text).
|
|
701
|
+
violation_id_text(Val, Val) :-
|
|
702
|
+
string(Val),
|
|
703
|
+
!.
|
|
704
|
+
violation_id_text(Val, Text) :-
|
|
705
|
+
atom(Val),
|
|
706
|
+
!,
|
|
707
|
+
atom_string(Val, Text).
|
|
708
|
+
violation_id_text(Val, Text) :-
|
|
709
|
+
term_string(Val, Text).
|
|
557
710
|
|
|
558
711
|
violation_source(EntityId, Type, Source) :-
|
|
559
712
|
( kb_entity(EntityId, Type, Props),
|
package/src/kb.pl
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
with_kb_mutex/1,
|
|
8
8
|
kb_assert_entity/2,
|
|
9
9
|
kb_assert_entity_no_audit/2,
|
|
10
|
-
kb_log_entity_upsert/
|
|
10
|
+
kb_log_entity_upsert/3,
|
|
11
11
|
kb_retract_entity/1,
|
|
12
|
+
kb_retract_entity/3,
|
|
12
13
|
kb_entity/3,
|
|
13
14
|
kb_entities_by_source/2,
|
|
14
15
|
kb_assert_relationship/4,
|
|
@@ -254,8 +255,13 @@ load_kb_pl_files(Directory) :-
|
|
|
254
255
|
% Assert an entity into the KB with audit logging.
|
|
255
256
|
% Properties is a list of Key=Value pairs.
|
|
256
257
|
kb_assert_entity(Type, Props) :-
|
|
258
|
+
memberchk(id=Id, Props),
|
|
259
|
+
( once(kb_entity(Id, _, _))
|
|
260
|
+
-> ChangeKind = updated
|
|
261
|
+
; ChangeKind = created
|
|
262
|
+
),
|
|
257
263
|
kb_assert_entity_no_audit(Type, Props),
|
|
258
|
-
kb_log_entity_upsert(Type, Props).
|
|
264
|
+
kb_log_entity_upsert(ChangeKind, Type, Props).
|
|
259
265
|
|
|
260
266
|
%% kb_assert_entity_no_audit(+Type, +Properties)
|
|
261
267
|
% Assert an entity RDF payload without recording audit side effects.
|
|
@@ -284,19 +290,30 @@ kb_assert_entity_no_audit(Type, Props) :-
|
|
|
284
290
|
)
|
|
285
291
|
)).
|
|
286
292
|
|
|
287
|
-
%% kb_log_entity_upsert(+Type, +Properties)
|
|
293
|
+
%% kb_log_entity_upsert(+ChangeKind, +Type, +Properties)
|
|
288
294
|
% Append the audit entry for a successfully committed entity upsert.
|
|
289
|
-
kb_log_entity_upsert(Type, Props) :-
|
|
295
|
+
kb_log_entity_upsert(ChangeKind, Type, Props) :-
|
|
290
296
|
memberchk(id=Id, Props),
|
|
297
|
+
memberchk(ChangeKind, [created, updated]),
|
|
291
298
|
with_kb_mutex((
|
|
292
299
|
get_time(Timestamp),
|
|
293
300
|
format_time(atom(TS), '%FT%T%:z', Timestamp),
|
|
294
|
-
assert_changeset(TS, upsert, Id, Type-Props)
|
|
301
|
+
assert_changeset(TS, upsert, Id, Type-[change_kind=ChangeKind|Props])
|
|
295
302
|
)).
|
|
296
303
|
|
|
297
304
|
%% kb_retract_entity(+Id)
|
|
298
305
|
% Remove an entity from the KB with audit logging.
|
|
299
306
|
kb_retract_entity(Id) :-
|
|
307
|
+
( once(kb_entity(Id, Type, Props))
|
|
308
|
+
-> entity_delete_audit_props(Id, Props, AuditProps)
|
|
309
|
+
; Type = unknown,
|
|
310
|
+
AuditProps = [id=Id]
|
|
311
|
+
),
|
|
312
|
+
kb_retract_entity(Id, Type, AuditProps).
|
|
313
|
+
|
|
314
|
+
%% kb_retract_entity(+Id, +Type, +AuditProps)
|
|
315
|
+
% Remove an entity from the KB and log the provided delete payload.
|
|
316
|
+
kb_retract_entity(Id, Type, AuditProps) :-
|
|
300
317
|
kb_graph(Graph),
|
|
301
318
|
with_kb_mutex((
|
|
302
319
|
% Create entity URI
|
|
@@ -306,9 +323,30 @@ kb_retract_entity(Id) :-
|
|
|
306
323
|
% Log to audit
|
|
307
324
|
get_time(Timestamp),
|
|
308
325
|
format_time(atom(TS), '%FT%T%:z', Timestamp),
|
|
309
|
-
assert_changeset(TS, delete, Id,
|
|
326
|
+
assert_changeset(TS, delete, Id, Type-AuditProps)
|
|
310
327
|
)).
|
|
311
328
|
|
|
329
|
+
entity_delete_audit_props(Id, Props, AuditProps) :-
|
|
330
|
+
findall(Key=Value,
|
|
331
|
+
( member(Key, [title, source, text_ref]),
|
|
332
|
+
memberchk(Key=RawValue, Props),
|
|
333
|
+
audit_property_value(RawValue, Value)
|
|
334
|
+
),
|
|
335
|
+
OptionalProps),
|
|
336
|
+
AuditProps = [id=Id|OptionalProps].
|
|
337
|
+
|
|
338
|
+
audit_property_value(RawValue, Value) :-
|
|
339
|
+
( RawValue = ^^(Inner, _)
|
|
340
|
+
-> Value = Inner
|
|
341
|
+
; RawValue = literal(type(_, Inner))
|
|
342
|
+
-> Value = Inner
|
|
343
|
+
; RawValue = literal(lang(_, Inner))
|
|
344
|
+
-> Value = Inner
|
|
345
|
+
; RawValue = literal(Inner)
|
|
346
|
+
-> Value = Inner
|
|
347
|
+
; Value = RawValue
|
|
348
|
+
).
|
|
349
|
+
|
|
312
350
|
%% kb_entity(?Id, ?Type, ?Properties)
|
|
313
351
|
% Query entities from the KB.
|
|
314
352
|
% Properties is unified with a list of Key=Value pairs.
|
|
@@ -356,12 +394,18 @@ convert_legacy_prop(Prop, Prop, true).
|
|
|
356
394
|
kb_entities_by_source(SourcePath, Ids) :-
|
|
357
395
|
findall(Id,
|
|
358
396
|
(kb_entity(Id, _Type, Props),
|
|
359
|
-
|
|
360
|
-
source_value_atom(RawSource, SourceAtom),
|
|
397
|
+
entity_source_atom(Props, SourceAtom),
|
|
361
398
|
sub_atom(SourceAtom, _, _, _, SourcePath)),
|
|
362
399
|
RawIds),
|
|
363
400
|
sort(RawIds, Ids).
|
|
364
401
|
|
|
402
|
+
entity_source_atom(Props, SourceAtom) :-
|
|
403
|
+
( memberchk(sourceFile=RawSourceFile, Props)
|
|
404
|
+
-> source_value_atom(RawSourceFile, SourceAtom)
|
|
405
|
+
; memberchk(source=RawSource, Props),
|
|
406
|
+
source_value_atom(RawSource, SourceAtom)
|
|
407
|
+
).
|
|
408
|
+
|
|
365
409
|
source_value_atom(Value, Atom) :-
|
|
366
410
|
( atom(Value)
|
|
367
411
|
-> Atom = Value
|
package/src/status.pl
CHANGED
|
@@ -61,7 +61,8 @@ synced_at(DataFile, SyncedAt) :-
|
|
|
61
61
|
!,
|
|
62
62
|
time_file(DataFile, Timestamp),
|
|
63
63
|
format_time(atom(SyncedAt), '%FT%TZ', Timestamp).
|
|
64
|
-
|
|
64
|
+
% Before the first successful sync there is no kb.rdf, so the public JSON contract must expose syncedAt: null.
|
|
65
|
+
synced_at(_, null).
|
|
65
66
|
|
|
66
67
|
freshness_state(DataFile, true, stale) :-
|
|
67
68
|
exists_file(DataFile),
|