kibi-core 0.5.1 → 0.5.3

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/checks.pl +277 -7
  3. package/src/kb.pl +52 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
package/src/checks.pl CHANGED
@@ -15,7 +15,10 @@
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
- run_checks_json/0 % Entry point for JSON output
18
+ check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
19
+ check_strict_readiness/1, % Returns list of strict readiness audit violations
20
+ run_checks_json/0, % Entry point for JSON output
21
+ violation_id_text/2 % Extract text from entity ID term (exported for testing)
19
22
  ]).
20
23
 
21
24
  :- use_module(library(http/json)).
@@ -47,6 +50,8 @@ check_all(ViolationsDict) :-
47
50
  check_deprecated_adrs(DeprecatedADRs),
48
51
  check_domain_contradictions(Contradictions),
49
52
  check_strict_fact_shape(StrictFactShape),
53
+ check_strict_req_fact_pairing(StrictReqFactPairing),
54
+ check_strict_readiness(StrictReadiness),
50
55
  ViolationsDict = _{
51
56
  must_priority_coverage: MustPriority,
52
57
  symbol_coverage: SymbolCoverage,
@@ -56,7 +61,9 @@ check_all(ViolationsDict) :-
56
61
  required_fields: RequiredFields,
57
62
  deprecated_adr_no_successor: DeprecatedADRs,
58
63
  domain_contradictions: Contradictions,
59
- strict_fact_shape: StrictFactShape
64
+ strict_fact_shape: StrictFactShape,
65
+ strict_req_fact_pairing: StrictReqFactPairing,
66
+ strict_readiness: StrictReadiness
60
67
  }.
61
68
 
62
69
  %% check_must_priority_coverage(-Violations)
@@ -412,7 +419,8 @@ property_value_shape_error(Props, "Property value fact has multiple value fields
412
419
  length(Fields, Count),
413
420
  Count > 1.
414
421
  property_value_shape_error(Props, "Property value fact value_type does not match value field") :-
415
- memberchk(value_type=VT, Props),
422
+ memberchk(value_type=RawVT, Props),
423
+ normalize_term_atom(RawVT, VT),
416
424
  \+ value_type_matches_field(VT, Props).
417
425
 
418
426
  % is_value_field(+Field)
@@ -440,11 +448,255 @@ check_domain_contradictions(Violations) :-
440
448
  ),
441
449
  ( contradicting_reqs(ReqA, ReqB, Reason),
442
450
  format(string(EntityId), "~w/~w", [ReqA, ReqB]),
443
- Description = Reason
451
+ format(string(Description), "~w [strict-readiness: contradiction-ready]", [Reason])
444
452
  ),
445
453
  Violations
446
454
  ).
447
455
 
456
+ %% check_strict_req_fact_pairing(-Violations)
457
+ % Finds current requirements attempting strict-lane modeling with incomplete
458
+ % subject/property pairing or wrong-lane fact targets.
459
+ check_strict_req_fact_pairing(Violations) :-
460
+ findall(
461
+ Violation,
462
+ strict_req_fact_pairing_violation(Violation),
463
+ Violations0
464
+ ),
465
+ sort(Violations0, Violations).
466
+
467
+ strict_req_fact_pairing_violation(violation(
468
+ 'strict-req-fact-pairing',
469
+ ReqId,
470
+ Description,
471
+ Suggestion,
472
+ Source
473
+ )) :-
474
+ strict_req_fact_pairing_issue(ReqId, Description, Suggestion),
475
+ violation_source(ReqId, req, Source).
476
+
477
+ strict_req_fact_pairing_issue(
478
+ ReqId,
479
+ Description,
480
+ "Add a property_value fact via requires_property for the same subject_key"
481
+ ) :-
482
+ kb:current_req(ReqId),
483
+ kb_relationship(constrains, ReqId, SubjectFactId),
484
+ strict_req_fact_pairing_fact_kind(SubjectFactId, subject),
485
+ kb:fact_subject_key(SubjectFactId, SubjectKey),
486
+ \+ kb:effective_req_property_fact(
487
+ ReqId,
488
+ SubjectKey,
489
+ _PropertyFactId,
490
+ _PropertyKey,
491
+ _Operator,
492
+ _ValueType,
493
+ _Value,
494
+ _Unit,
495
+ _Scope,
496
+ _Polarity,
497
+ _ValidFrom,
498
+ _ValidTo
499
+ ),
500
+ format(
501
+ string(Description),
502
+ "Requirement constrains ~w (~w) but has no matching strict requires_property fact",
503
+ [SubjectFactId, SubjectKey]
504
+ ).
505
+
506
+ strict_req_fact_pairing_issue(
507
+ ReqId,
508
+ Description,
509
+ "Add a subject fact via constrains for the same subject_key or remove the mismatched requires_property link"
510
+ ) :-
511
+ kb:current_req(ReqId),
512
+ kb_relationship(requires_property, ReqId, PropertyFactId),
513
+ strict_req_fact_pairing_fact_kind(PropertyFactId, property_value),
514
+ kb:fact_property_tuple(
515
+ PropertyFactId,
516
+ SubjectKey,
517
+ _PropertyKey,
518
+ _Operator,
519
+ _ValueType,
520
+ _Value,
521
+ _Unit,
522
+ _Scope,
523
+ _Polarity
524
+ ),
525
+ \+ kb:effective_req_property_fact(
526
+ ReqId,
527
+ SubjectKey,
528
+ PropertyFactId,
529
+ _MatchedPropertyKey,
530
+ _MatchedOperator,
531
+ _MatchedValueType,
532
+ _MatchedValue,
533
+ _MatchedUnit,
534
+ _MatchedScope,
535
+ _MatchedPolarity,
536
+ _MatchedValidFrom,
537
+ _MatchedValidTo
538
+ ),
539
+ format(
540
+ string(Description),
541
+ "Requirement requires_property ~w (~w) but has no matching strict subject fact via constrains",
542
+ [PropertyFactId, SubjectKey]
543
+ ).
544
+
545
+ strict_req_fact_pairing_issue(
546
+ ReqId,
547
+ Description,
548
+ "Use a subject fact with constrains for contradiction-safe semantics; keep non-subject facts out of strict pairing"
549
+ ) :-
550
+ kb:current_req(ReqId),
551
+ kb_relationship(constrains, ReqId, FactId),
552
+ strict_req_fact_pairing_fact_kind(FactId, Kind),
553
+ Kind \= subject,
554
+ strict_req_fact_pairing_kind_label(Kind, KindLabel),
555
+ format(
556
+ string(Description),
557
+ "Requirement links ~w via constrains using ~w; contradiction-safe constrains links must target subject facts",
558
+ [FactId, KindLabel]
559
+ ).
560
+
561
+ strict_req_fact_pairing_issue(
562
+ ReqId,
563
+ Description,
564
+ "Use a property_value fact with requires_property for contradiction-safe semantics; keep non-property facts out of strict pairing"
565
+ ) :-
566
+ kb:current_req(ReqId),
567
+ kb_relationship(requires_property, ReqId, FactId),
568
+ strict_req_fact_pairing_fact_kind(FactId, Kind),
569
+ Kind \= property_value,
570
+ strict_req_fact_pairing_kind_label(Kind, KindLabel),
571
+ format(
572
+ string(Description),
573
+ "Requirement links ~w via requires_property using ~w; contradiction-safe requires_property links must target property_value facts",
574
+ [FactId, KindLabel]
575
+ ).
576
+
577
+ strict_req_fact_pairing_fact_kind(FactId, Kind) :-
578
+ kb_entity(FactId, fact, Props),
579
+ ( memberchk(fact_kind=RawKind, Props)
580
+ -> normalize_term_atom(RawKind, Kind)
581
+ ; Kind = legacy
582
+ ).
583
+
584
+ strict_req_fact_pairing_kind_label(legacy, "a legacy fact without fact_kind").
585
+ strict_req_fact_pairing_kind_label(Kind, Label) :-
586
+ format(string(Label), "a fact_kind=~w fact", [Kind]).
587
+
588
+ %% check_strict_readiness(-Violations)
589
+ % Reports current strict-readiness levels for requirements that are still not
590
+ % contradiction-ready. Legacy and prose-only requirements remain audit-only and
591
+ % are intentionally not treated as contradictions.
592
+ check_strict_readiness(Violations) :-
593
+ findall(
594
+ Violation,
595
+ strict_readiness_violation(Violation),
596
+ Violations0
597
+ ),
598
+ sort(Violations0, Violations).
599
+
600
+ strict_readiness_violation(violation(
601
+ 'strict-readiness',
602
+ ReqId,
603
+ Description,
604
+ Suggestion,
605
+ Source
606
+ )) :-
607
+ strict_readiness_issue(ReqId, Description, Suggestion),
608
+ violation_source(ReqId, req, Source).
609
+
610
+ strict_readiness_issue(
611
+ ReqId,
612
+ "Strict readiness: not-ready (prose-only). Requirement has no fact links, so contradiction checks skip it.",
613
+ "Add a subject fact via constrains and a property_value fact via requires_property to model contradiction-safe semantics"
614
+ ) :-
615
+ kb_entity(ReqId, req, _),
616
+ strict_readiness_level(ReqId, prose_only).
617
+
618
+ strict_readiness_issue(
619
+ ReqId,
620
+ "Strict readiness: not-ready (traceable). Requirement is linked to facts only through legacy or non-strict links, so contradiction checks skip it.",
621
+ "Replace prose or legacy fact links with a subject fact via constrains and a property_value fact via requires_property"
622
+ ) :-
623
+ kb_entity(ReqId, req, _),
624
+ strict_readiness_level(ReqId, traceable).
625
+
626
+ strict_readiness_issue(
627
+ ReqId,
628
+ Description,
629
+ "Add a matching property_value fact via requires_property for the same subject_key"
630
+ ) :-
631
+ kb_entity(ReqId, req, _),
632
+ strict_readiness_level(ReqId, has_subject),
633
+ strict_readiness_primary_subject(ReqId, SubjectFactId, SubjectKey),
634
+ format(
635
+ string(Description),
636
+ "Strict readiness: not-ready (has-subject). Requirement constrains ~w (~w) but has no matching strict requires_property fact, so contradiction checks skip it.",
637
+ [SubjectFactId, SubjectKey]
638
+ ).
639
+
640
+ strict_readiness_issue(
641
+ ReqId,
642
+ "Strict readiness: not-ready (strict-ready). Requirement has strict subject and property facts but no contradiction-ready matched pair, so contradiction checks still skip it.",
643
+ "Align constrains and requires_property on the same subject_key, and keep the requirement current if it should participate in contradiction checks"
644
+ ) :-
645
+ kb_entity(ReqId, req, _),
646
+ strict_readiness_level(ReqId, strict_ready).
647
+
648
+ strict_readiness_level(ReqId, contradiction_ready) :-
649
+ kb:current_req(ReqId),
650
+ kb:effective_req_property_fact(
651
+ ReqId,
652
+ _SubjectKey,
653
+ _FactId,
654
+ _PropertyKey,
655
+ _Operator,
656
+ _ValueType,
657
+ _Value,
658
+ _Unit,
659
+ _Scope,
660
+ _Polarity,
661
+ _ValidFrom,
662
+ _ValidTo
663
+ ),
664
+ !.
665
+ strict_readiness_level(ReqId, strict_ready) :-
666
+ strict_readiness_has_strict_subject(ReqId),
667
+ strict_readiness_has_strict_property(ReqId),
668
+ !.
669
+ strict_readiness_level(ReqId, has_subject) :-
670
+ strict_readiness_has_strict_subject(ReqId),
671
+ !.
672
+ strict_readiness_level(ReqId, traceable) :-
673
+ strict_readiness_has_fact_link(ReqId),
674
+ !.
675
+ strict_readiness_level(_ReqId, prose_only).
676
+
677
+ strict_readiness_has_fact_link(ReqId) :-
678
+ kb_relationship(constrains, ReqId, FactId),
679
+ kb_entity(FactId, fact, _),
680
+ !.
681
+ strict_readiness_has_fact_link(ReqId) :-
682
+ kb_relationship(requires_property, ReqId, FactId),
683
+ kb_entity(FactId, fact, _),
684
+ !.
685
+
686
+ strict_readiness_has_strict_subject(ReqId) :-
687
+ strict_readiness_primary_subject(ReqId, _FactId, _SubjectKey),
688
+ !.
689
+
690
+ strict_readiness_primary_subject(ReqId, FactId, SubjectKey) :-
691
+ kb_relationship(constrains, ReqId, FactId),
692
+ strict_req_fact_pairing_fact_kind(FactId, subject),
693
+ kb:fact_subject_key(FactId, SubjectKey).
694
+
695
+ strict_readiness_has_strict_property(ReqId) :-
696
+ kb_relationship(requires_property, ReqId, FactId),
697
+ strict_req_fact_pairing_fact_kind(FactId, property_value),
698
+ !.
699
+
448
700
  %% run_checks_json
449
701
  % Entry point for JSON output. Prints all violations as JSON to stdout.
450
702
  run_checks_json :-
@@ -492,6 +744,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
492
744
  check_deprecated_adrs(DeprecatedADRs),
493
745
  check_domain_contradictions(Contradictions),
494
746
  check_strict_fact_shape(StrictFactShape),
747
+ check_strict_req_fact_pairing(StrictReqFactPairing),
748
+ check_strict_readiness(StrictReadiness),
495
749
  ViolationsDict = _{
496
750
  must_priority_coverage: MustPriority,
497
751
  symbol_coverage: SymbolCoverage,
@@ -501,7 +755,9 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
501
755
  required_fields: RequiredFields,
502
756
  deprecated_adr_no_successor: DeprecatedADRs,
503
757
  domain_contradictions: Contradictions,
504
- strict_fact_shape: StrictFactShape
758
+ strict_fact_shape: StrictFactShape,
759
+ strict_req_fact_pairing: StrictReqFactPairing,
760
+ strict_readiness: StrictReadiness
505
761
  }.
506
762
 
507
763
  %% violations_dict_to_json(+ViolationsDict, -JsonDict)
@@ -552,8 +808,22 @@ violation_text(Val, Text) :-
552
808
  term_string(Val, Text).
553
809
 
554
810
  violation_id_text(Val, Text) :-
555
- normalize_term_atom(Val, Atom),
556
- atom_string(Atom, Text).
811
+ nonvar(Val),
812
+ Val =.. ['^^', Inner, _Type],
813
+ !,
814
+ violation_id_text(Inner, Text).
815
+ violation_id_text(literal(type(_, Val)), Text) :-
816
+ !,
817
+ violation_id_text(Val, Text).
818
+ violation_id_text(Val, Val) :-
819
+ string(Val),
820
+ !.
821
+ violation_id_text(Val, Text) :-
822
+ atom(Val),
823
+ !,
824
+ atom_string(Val, Text).
825
+ violation_id_text(Val, Text) :-
826
+ term_string(Val, Text).
557
827
 
558
828
  violation_source(EntityId, Type, Source) :-
559
829
  ( 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/2,
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, null)
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
- memberchk(source=RawSource, Props),
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