kibi-core 0.18.1 → 0.19.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.18.1",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
package/src/discovery.pl CHANGED
@@ -513,17 +513,34 @@ hidden_provenance_stub(RequestedTags, Type, Props) :-
513
513
  \+ memberchk('bootstrap:provenance-stub', RequestedTags),
514
514
  provenance_stub_props(Type, Props).
515
515
 
516
+ % A requested missing/present relationship is satisfied by its documented
517
+ % inverse as well (docs/entity-schema.md: `validates` is the inverse edge of
518
+ % `verified_by` for req/scenario <-> test links), so a requirement whose test
519
+ % `validates` it is not a `verified_by` gap. relationshipCounts stay per name.
520
+ % implements REQ-mcp-search-discovery
516
521
  relationships_missing(_Id, []).
517
522
  relationships_missing(Id, [Relationship|Rest]) :-
518
- relationship_count(Id, Relationship, 0),
523
+ linked_relationship_count(Id, Relationship, 0),
519
524
  relationships_missing(Id, Rest).
520
525
 
526
+ % implements REQ-mcp-search-discovery
521
527
  relationships_present(_Id, []).
522
528
  relationships_present(Id, [Relationship|Rest]) :-
523
- relationship_count(Id, Relationship, Count),
529
+ linked_relationship_count(Id, Relationship, Count),
524
530
  Count > 0,
525
531
  relationships_present(Id, Rest).
526
532
 
533
+ inverse_relationship(verified_by, validates).
534
+ inverse_relationship(validates, verified_by).
535
+
536
+ linked_relationship_count(Id, Relationship, Count) :-
537
+ relationship_count(Id, Relationship, Direct),
538
+ ( inverse_relationship(Relationship, Inverse)
539
+ -> relationship_count(Id, Inverse, Reverse)
540
+ ; Reverse = 0
541
+ ),
542
+ Count is Direct + Reverse.
543
+
527
544
  relationship_counts(Id, CountsDict) :-
528
545
  findall(Relationship-Count,
529
546
  (relationship_type(Relationship), relationship_count(Id, Relationship, Count)),
package/src/kb.pl CHANGED
@@ -135,6 +135,7 @@ kb_uri('urn-kibi:').
135
135
  % the memo never serves a property list the store no longer holds.
136
136
  :- dynamic kb_entity_props_memo/2.
137
137
  :- dynamic kb_entity_props_memo_key/1.
138
+ :- dynamic kb_decision_signature_memo/2.
138
139
  :- dynamic kb_index_verified_key/1.
139
140
  :- dynamic entity/4. % Support legacy .pl file format (Type, Id, Title, Props)
140
141
 
@@ -361,6 +362,7 @@ kb_detach :-
361
362
  retractall(kb_index_verified_key(_)),
362
363
  retractall(kb_entity_props_memo(_, _)),
363
364
  retractall(kb_entity_props_memo_key(_)),
365
+ retractall(kb_decision_signature_memo(_, _)),
364
366
  retractall(kb_index_entity(_, _)),
365
367
  retractall(kb_index_type(_, _)),
366
368
  retractall(kb_index_tag(_, _)),
@@ -2662,6 +2664,154 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
2662
2664
  right: Right
2663
2665
  }.
2664
2666
 
2667
+ % implements REQ-kibi-truthful-consistency
2668
+ % Opposite decisions on the same case: two asserted facts of a predicate
2669
+ % whose key arguments determine a closed allow/deny decision argument (the
2670
+ % built-in permission_rule, or a project schema that declares key_arguments
2671
+ % and a `decision` argument) agree on every key argument but decide
2672
+ % differently, e.g. permission_rule(coach, read, x, allow) against
2673
+ % permission_rule(coach, read, x, deny). kb_model writes a denial as
2674
+ % decision deny with polarity deny, so a deny decision counts as a denial
2675
+ % whatever its polarity; an allow decision counts only when asserted.
2676
+ %
2677
+ % Cost: the search answer layer and coverage ask for the witnesses of each
2678
+ % requirement with the other side unbound. The pairing therefore starts
2679
+ % from whichever requirement is bound, filters its facts against the
2680
+ % decision signatures (memoized per KB generation, so the project schemas
2681
+ % are read once rather than once per fact), and pre-binds the partner's key
2682
+ % arguments before enumerating partner requirements.
2683
+ req_conflict_witness(ReqA, ReqB, Witness) :-
2684
+ decision_signatures(Signatures),
2685
+ Signatures \== [],
2686
+ decision_conflict_pair(Signatures, ReqA, ReqB, FactA, FactB, Namespace, Name,
2687
+ ArgsA, ArgsB, PolarityA, PolarityB, KeyPositions, DecisionPosition),
2688
+ length(ArgsA, Arity),
2689
+ nth1(DecisionPosition, ArgsA, DecisionA),
2690
+ nth1(DecisionPosition, ArgsB, DecisionB),
2691
+ DecisionA \== DecisionB,
2692
+ effective_decision(DecisionA, PolarityA, EffectA),
2693
+ effective_decision(DecisionB, PolarityB, EffectB),
2694
+ EffectA \== EffectB,
2695
+ findall(KeyValue, (member(Position, KeyPositions), nth1(Position, ArgsA, KeyValue)), KeyValues),
2696
+ atomic_list_concat(KeyValues, ',', KeyText),
2697
+ format(
2698
+ string(Reason),
2699
+ "Decision conflict on ~w:~w(~w): ~w decides ~w while ~w decides ~w",
2700
+ [Namespace, Name, KeyText, ReqA, EffectA, ReqB, EffectB]
2701
+ ),
2702
+ predicate_conflict_side(ReqA, FactA, Namespace, Name, ArgsA, PolarityA, Left),
2703
+ predicate_conflict_side(ReqB, FactB, Namespace, Name, ArgsB, PolarityB, Right),
2704
+ findall(SchemaId, predicate_schema(SchemaId, Namespace, Name, Arity, _Names, _Types), SchemaIds0),
2705
+ sort(SchemaIds0, SchemaIds),
2706
+ Witness = _{
2707
+ kind: predicate,
2708
+ status: contradiction,
2709
+ requirements: [ReqA, ReqB],
2710
+ reason: Reason,
2711
+ predicateNamespace: Namespace,
2712
+ predicateName: Name,
2713
+ predicateArgs: ArgsA,
2714
+ keyArguments: KeyValues,
2715
+ schemaIds: SchemaIds,
2716
+ left: Left,
2717
+ right: Right
2718
+ }.
2719
+
2720
+ %% decision_conflict_pair(+Signatures, ?ReqA, ?ReqB, -FactA, -FactB, -Namespace, -Name, -ArgsA, -ArgsB, -PolarityA, -PolarityB, -KeyPositions, -DecisionPosition)
2721
+ % Two decision-predicate facts of current requirements ReqA @< ReqB that
2722
+ % agree on every key argument. Starts from the bound requirement.
2723
+ decision_conflict_pair(Signatures, ReqA, ReqB, FactA, FactB, Namespace, Name,
2724
+ ArgsA, ArgsB, PolarityA, PolarityB, KeyPositions, DecisionPosition) :-
2725
+ ( var(ReqA), nonvar(ReqB)
2726
+ -> decision_req_fact(Signatures, ReqB, FactB, Namespace, Name, ArgsB, PolarityB, KeyPositions, DecisionPosition),
2727
+ length(ArgsB, Arity),
2728
+ length(ArgsA, Arity),
2729
+ bind_key_arguments(KeyPositions, ArgsB, ArgsA),
2730
+ current_req(ReqA),
2731
+ ReqA @< ReqB,
2732
+ effective_req_predicate(ReqA, FactA, Namespace, Name, ArgsA, PolarityA)
2733
+ ; decision_req_fact(Signatures, ReqA, FactA, Namespace, Name, ArgsA, PolarityA, KeyPositions, DecisionPosition),
2734
+ length(ArgsA, Arity),
2735
+ length(ArgsB, Arity),
2736
+ bind_key_arguments(KeyPositions, ArgsA, ArgsB),
2737
+ current_req(ReqB),
2738
+ ReqA @< ReqB,
2739
+ effective_req_predicate(ReqB, FactB, Namespace, Name, ArgsB, PolarityB)
2740
+ ),
2741
+ FactA \= FactB.
2742
+
2743
+ decision_req_fact(Signatures, ReqId, FactId, Namespace, Name, Args, Polarity, KeyPositions, DecisionPosition) :-
2744
+ current_req(ReqId),
2745
+ effective_req_predicate(ReqId, FactId, Namespace, Name, Args, Polarity),
2746
+ length(Args, Arity),
2747
+ memberchk(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures).
2748
+
2749
+ %% decision_predicate(?Namespace, ?Name, ?Arity, -KeyPositions, -DecisionPosition)
2750
+ % A predicate whose key arguments (1-based KeyPositions) determine one
2751
+ % allow/deny decision argument. A project-local predicate_schema decides
2752
+ % this for its own signature: it must declare key_arguments and name a
2753
+ % non-key argument `decision`. Without a project schema the built-in
2754
+ % catalog entry applies.
2755
+ decision_predicate(Namespace, Name, Arity, KeyPositions, DecisionPosition) :-
2756
+ decision_signatures(Signatures),
2757
+ member(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures).
2758
+
2759
+ %% decision_signatures(-Signatures)
2760
+ % All decision predicates of the current KB, memoized per graph, RDF
2761
+ % generation and legacy fact count like the entity property memo.
2762
+ decision_signatures(Signatures) :-
2763
+ index_verification_key(decision_signatures, Key),
2764
+ ( kb_decision_signature_memo(Key, Cached)
2765
+ -> Signatures = Cached
2766
+ ; compute_decision_signatures(Computed),
2767
+ retractall(kb_decision_signature_memo(_, _)),
2768
+ assertz(kb_decision_signature_memo(Key, Computed)),
2769
+ Signatures = Computed
2770
+ ).
2771
+
2772
+ compute_decision_signatures(Signatures) :-
2773
+ findall(Namespace-Name-Arity,
2774
+ predicate_schema(_SchemaId, Namespace, Name, Arity, _Names, _Types),
2775
+ SchemaSignatures0),
2776
+ sort(SchemaSignatures0, SchemaSignatures),
2777
+ findall(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition),
2778
+ ( member(Namespace-Name-Arity, SchemaSignatures),
2779
+ schema_decision_positions(Namespace, Name, Arity, KeyPositions, DecisionPosition)
2780
+ ),
2781
+ FromSchemas),
2782
+ findall(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition),
2783
+ ( builtin_decision_predicate(Namespace, Name, Arity, KeyPositions, DecisionPosition),
2784
+ \+ memberchk(Namespace-Name-Arity, SchemaSignatures)
2785
+ ),
2786
+ Builtins),
2787
+ append(FromSchemas, Builtins, Signatures).
2788
+
2789
+ schema_decision_positions(Namespace, Name, Arity, KeyPositions, DecisionPosition) :-
2790
+ predicate_schema_keys(Namespace, Name, Arity, KeyPositions),
2791
+ predicate_schema(_SchemaId, Namespace, Name, Arity, ArgumentNames, _ArgumentTypes),
2792
+ nth1(DecisionPosition, ArgumentNames, decision),
2793
+ \+ memberchk(DecisionPosition, KeyPositions),
2794
+ !.
2795
+
2796
+ %% bind_key_arguments(+KeyPositions, +ArgsA, ?ArgsB)
2797
+ % Unify ArgsB with ArgsA on every key position.
2798
+ bind_key_arguments([], _ArgsA, _ArgsB).
2799
+ bind_key_arguments([Position|Positions], ArgsA, ArgsB) :-
2800
+ nth1(Position, ArgsA, Value),
2801
+ nth1(Position, ArgsB, Value),
2802
+ bind_key_arguments(Positions, ArgsA, ArgsB).
2803
+
2804
+ %% builtin_decision_predicate(?Namespace, ?Name, ?Arity, ?KeyPositions, ?DecisionPosition)
2805
+ % Built-in catalog predicates with a closed allow/deny decision. Keep in
2806
+ % step with key_arguments in packages/cli/src/operations/modeling/
2807
+ % predicate-catalog-4.ts (permission_rule: actor, action, resource).
2808
+ builtin_decision_predicate(default, permission_rule, 4, [1, 2, 3], 4).
2809
+
2810
+ %% effective_decision(+Decision, +Polarity, -Effect)
2811
+ effective_decision(allow, assert, allow).
2812
+ effective_decision(allow, deny, deny).
2813
+ effective_decision(deny, _Polarity, deny).
2814
+
2665
2815
  property_conflict_side(ReqId, FactId, SubjectKey, PropertyKey, Operator, ValueType, Value, Unit, Scope, Polarity, ValidFrom, ValidTo, Side) :-
2666
2816
  entity_evidence_source(ReqId, req, RequirementSource),
2667
2817
  fact_evidence_fields(FactId, FactSource, ClaimKey, ClaimText, _CanonicalKey),