kibi-core 0.17.2 → 0.18.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/src/discovery.pl +49 -6
- package/src/kb.pl +31 -0
- package/src/requirement_proof.pl +55 -3
- package/src/rule_registry.pl +8 -0
package/package.json
CHANGED
package/src/discovery.pl
CHANGED
|
@@ -32,18 +32,32 @@
|
|
|
32
32
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
33
33
|
|
|
34
34
|
find_gaps_json(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Limit, Offset, JsonString) :-
|
|
35
|
+
% One pass over the entities: every candidate carries whether it is a
|
|
36
|
+
% provenance stub, so the rows and the stub count share the enumeration.
|
|
35
37
|
findall(
|
|
36
|
-
Row,
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
Row-Stub,
|
|
39
|
+
matching_gap_candidate(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row, Stub),
|
|
40
|
+
Candidates
|
|
39
41
|
),
|
|
42
|
+
findall(Row, (member(Row-Stub, Candidates), gap_row_visible(Tags, Stub)), Rows0),
|
|
40
43
|
sort_dict_rows(Rows0, SortedRows),
|
|
41
44
|
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
42
45
|
length(SortedRows, Count),
|
|
46
|
+
% Provenance stubs that match the same filters are counted apart
|
|
47
|
+
% (summary.provenanceStubs) whether or not the requested tags let them
|
|
48
|
+
% into the rows, so a caller can tell "no gaps" from "only stubs".
|
|
49
|
+
% implements REQ-bootstrap-provenance-stubs
|
|
50
|
+
aggregate_all(count, member(_-true, Candidates), StubCount),
|
|
51
|
+
Summary = _{total: Count, provenanceStubs: StubCount},
|
|
43
52
|
status_meta_dict(Meta),
|
|
44
|
-
Response = _{rows: Rows, count: Count, meta: Meta},
|
|
53
|
+
Response = _{rows: Rows, count: Count, summary: Summary, meta: Meta},
|
|
45
54
|
dict_json_string(Response, JsonString).
|
|
46
55
|
|
|
56
|
+
% A stub enters the rows only when the caller asked for its tag.
|
|
57
|
+
gap_row_visible(_Tags, false).
|
|
58
|
+
gap_row_visible(Tags, true) :-
|
|
59
|
+
memberchk('bootstrap:provenance-stub', Tags).
|
|
60
|
+
|
|
47
61
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, JsonString) :-
|
|
48
62
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, unknown, '1970-01-01T00:00:00Z', 604800, JsonString).
|
|
49
63
|
|
|
@@ -458,9 +472,15 @@ graph_expand_json(SeedIds, Relationships, Direction, Depth, EntityTypes, MaxNode
|
|
|
458
472
|
dict_json_string(Response, JsonString).
|
|
459
473
|
|
|
460
474
|
matching_gap_row(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row) :-
|
|
475
|
+
matching_gap_candidate(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row, Stub),
|
|
476
|
+
gap_row_visible(Tags, Stub).
|
|
477
|
+
|
|
478
|
+
% implements REQ-bootstrap-provenance-stubs
|
|
479
|
+
matching_gap_candidate(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row, Stub) :-
|
|
461
480
|
kb_entity(Id, Type, Props),
|
|
462
481
|
matches_type(TypeFilter, Type),
|
|
463
482
|
matches_tags(Tags, Props),
|
|
483
|
+
(provenance_stub_props(Type, Props) -> Stub = true ; Stub = false),
|
|
464
484
|
matches_source(SourceFilter, Props),
|
|
465
485
|
relationships_missing(Id, MissingRelationships),
|
|
466
486
|
relationships_present(Id, PresentRelationships),
|
|
@@ -477,6 +497,22 @@ matching_gap_row(TypeFilter, MissingRelationships, PresentRelationships, Tags, S
|
|
|
477
497
|
source: Source
|
|
478
498
|
}.
|
|
479
499
|
|
|
500
|
+
%% provenance_stub_props(+Type, +Props)
|
|
501
|
+
% A bootstrap provider fact that records only where an entry came from
|
|
502
|
+
% (tagged bootstrap:provenance-stub). It is not knowledge: gap rows and
|
|
503
|
+
% type coverage counts leave it out unless the caller asks for the tag.
|
|
504
|
+
% implements REQ-bootstrap-provenance-stubs
|
|
505
|
+
provenance_stub_props(fact, Props) :-
|
|
506
|
+
memberchk(tags=Tags, Props),
|
|
507
|
+
is_list(Tags),
|
|
508
|
+
member(Tag, Tags),
|
|
509
|
+
kb:normalize_term_atom(Tag, 'bootstrap:provenance-stub'),
|
|
510
|
+
!.
|
|
511
|
+
|
|
512
|
+
hidden_provenance_stub(RequestedTags, Type, Props) :-
|
|
513
|
+
\+ memberchk('bootstrap:provenance-stub', RequestedTags),
|
|
514
|
+
provenance_stub_props(Type, Props).
|
|
515
|
+
|
|
480
516
|
relationships_missing(_Id, []).
|
|
481
517
|
relationships_missing(Id, [Relationship|Rest]) :-
|
|
482
518
|
relationship_count(Id, Relationship, 0),
|
|
@@ -537,7 +573,8 @@ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, _VerificationSna
|
|
|
537
573
|
Pairs),
|
|
538
574
|
maplist(type_pair_row, Pairs, Rows),
|
|
539
575
|
length(Rows, Total),
|
|
540
|
-
|
|
576
|
+
aggregate_all(count, provenance_stub_entity(_), StubCount),
|
|
577
|
+
Summary = _{total: Total, provenanceStubs: StubCount}.
|
|
541
578
|
|
|
542
579
|
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
543
580
|
!,
|
|
@@ -890,10 +927,16 @@ must_priority(Priority) :-
|
|
|
890
927
|
downcase_atom(Priority, Lowercase),
|
|
891
928
|
sub_atom(Lowercase, _, 4, 0, must).
|
|
892
929
|
|
|
930
|
+
% Provenance stubs are counted apart (summary.provenanceStubs), never as facts.
|
|
931
|
+
% implements REQ-bootstrap-provenance-stubs
|
|
893
932
|
type_entity_count(Type, Count) :-
|
|
894
|
-
setof(Id, Props^kb_entity(Id, Type, Props), Ids),
|
|
933
|
+
setof(Id, Props^(kb_entity(Id, Type, Props), \+ provenance_stub_props(Type, Props)), Ids),
|
|
895
934
|
length(Ids, Count).
|
|
896
935
|
|
|
936
|
+
provenance_stub_entity(Id) :-
|
|
937
|
+
kb_entity(Id, fact, Props),
|
|
938
|
+
provenance_stub_props(fact, Props).
|
|
939
|
+
|
|
897
940
|
type_pair_row(Type-Count, _{id: Type, type: Type, count: Count}).
|
|
898
941
|
|
|
899
942
|
bfs_layers(_Frontier, SeenNodes, SeenEdges, _Relationships, _Direction, 0, _MaxNodes, _MaxEdges, SeenNodes, SeenEdges, false) :- !.
|
package/src/kb.pl
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
kb_assert_entity_no_audit/2,
|
|
16
16
|
kb_commit_upsert/5,
|
|
17
17
|
kb_commit_upsert_batch/2,
|
|
18
|
+
kb_preview_upsert_contradiction/3,
|
|
18
19
|
kb_log_entity_upsert/3,
|
|
19
20
|
kb_retract_entity/1,
|
|
20
21
|
kb_retract_entity/3,
|
|
@@ -1136,6 +1137,36 @@ kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
|
1136
1137
|
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1137
1138
|
kb_commit_relationship_audits(Relationships).
|
|
1138
1139
|
|
|
1140
|
+
%% kb_preview_upsert_contradiction(+Type, +Properties, +Relationships)
|
|
1141
|
+
% implements REQ-kibi-operation-interface-parity
|
|
1142
|
+
% Stage one upsert in a transaction that is always rolled back and run the
|
|
1143
|
+
% commit-time requirement contradiction check against the staged KB, so a
|
|
1144
|
+
% kb_upsert dry run refuses exactly what the commit would refuse. Nothing is
|
|
1145
|
+
% written and nothing marks the branch dirty: the no-audit assertions only
|
|
1146
|
+
% touch the RDF store, which the transaction rolls back, and no audit entry,
|
|
1147
|
+
% snapshot save or journal flush follows. The entity index is not
|
|
1148
|
+
% transactional, so the staged id is re-indexed from the restored store.
|
|
1149
|
+
kb_preview_upsert_contradiction(Type, Props, Relationships) :-
|
|
1150
|
+
memberchk(id=Id, Props),
|
|
1151
|
+
with_kb_mutex(
|
|
1152
|
+
call_cleanup(
|
|
1153
|
+
catch(
|
|
1154
|
+
rdf_transaction((
|
|
1155
|
+
kb_assert_entity_no_audit(Type, Props),
|
|
1156
|
+
kb_commit_relationships_no_audit(Relationships),
|
|
1157
|
+
kb_maybe_check_req_contradiction(Type, Id, false),
|
|
1158
|
+
throw(kibi_preview_rolled_back)
|
|
1159
|
+
)),
|
|
1160
|
+
Caught,
|
|
1161
|
+
( Caught == kibi_preview_rolled_back
|
|
1162
|
+
-> true
|
|
1163
|
+
; throw(Caught)
|
|
1164
|
+
)
|
|
1165
|
+
),
|
|
1166
|
+
kb_refresh_entity_index(Id)
|
|
1167
|
+
)
|
|
1168
|
+
).
|
|
1169
|
+
|
|
1139
1170
|
kb_stage_upserts([], []).
|
|
1140
1171
|
kb_stage_upserts([
|
|
1141
1172
|
upsert(Type, Props, Relationships, SkipContradiction)|Rest
|
package/src/requirement_proof.pl
CHANGED
|
@@ -705,13 +705,22 @@ unique_evidence_by_test(Evidence0, Evidence) :-
|
|
|
705
705
|
sort(TestIds0, TestIds),
|
|
706
706
|
maplist(first_evidence_for_test(Evidence0), TestIds, Evidence).
|
|
707
707
|
|
|
708
|
+
% A test shared by scenarios with different accepted scopes keeps its receipt
|
|
709
|
+
% state from any scenario that accepts its scope, independent of scenario order.
|
|
710
|
+
% implements REQ-ui-pattern-component-proof
|
|
711
|
+
first_evidence_for_test(Evidence, TestId, Item) :-
|
|
712
|
+
member(Item, Evidence),
|
|
713
|
+
Item.testId == TestId,
|
|
714
|
+
Item.state \== not_end_to_end,
|
|
715
|
+
!.
|
|
708
716
|
first_evidence_for_test(Evidence, TestId, Item) :-
|
|
709
717
|
member(Item, Evidence),
|
|
710
718
|
Item.testId == TestId,
|
|
711
719
|
!.
|
|
712
720
|
|
|
713
721
|
scenario_passing_e2e_obligation(Context, TestObligation, Obligation) :-
|
|
714
|
-
|
|
722
|
+
scenario_accepted_scopes(TestObligation.scenarioId, AcceptedScopes),
|
|
723
|
+
maplist(test_receipt_evidence(Context, AcceptedScopes), TestObligation.tests, Evidence),
|
|
715
724
|
include(e2e_evidence, Evidence, E2eEvidence),
|
|
716
725
|
include(non_e2e_evidence, Evidence, NonEndToEndEvidence),
|
|
717
726
|
evidence_tests_with_state(E2eEvidence, passed, PassingE2eTests),
|
|
@@ -734,6 +743,7 @@ scenario_passing_e2e_obligation(Context, TestObligation, Obligation) :-
|
|
|
734
743
|
scenarioTestTargets: TestObligation.scenarioTestTargets,
|
|
735
744
|
invalidScenarioTestTargets: TestObligation.invalidScenarioTestTargets,
|
|
736
745
|
tests: TestObligation.tests,
|
|
746
|
+
acceptedScopes: AcceptedScopes,
|
|
737
747
|
e2eTests: E2eTests,
|
|
738
748
|
nonEndToEndTests: NonEndToEndTests,
|
|
739
749
|
evidence: Evidence,
|
|
@@ -747,6 +757,42 @@ scenario_passing_e2e_obligation(Context, TestObligation, Obligation) :-
|
|
|
747
757
|
snapshotUnavailableTests: SnapshotUnavailableTests
|
|
748
758
|
}.
|
|
749
759
|
|
|
760
|
+
%% scenario_accepted_scopes(+ScenarioId, -Scopes)
|
|
761
|
+
% End-to-end evidence proves every scenario. A scenario whose specifying
|
|
762
|
+
% requirements are all grounded only in component-scope UI pattern predicates
|
|
763
|
+
% may also be proven by a unit or integration test that renders the component
|
|
764
|
+
% in isolation, with the same receipt rules (valid, fresh, passing).
|
|
765
|
+
% implements REQ-ui-pattern-component-proof
|
|
766
|
+
scenario_accepted_scopes(ScenarioId, [unit, integration, end_to_end]) :-
|
|
767
|
+
component_scope_scenario(ScenarioId),
|
|
768
|
+
!.
|
|
769
|
+
scenario_accepted_scopes(_ScenarioId, [end_to_end]).
|
|
770
|
+
|
|
771
|
+
%% component_scope_predicate(?Name)
|
|
772
|
+
% Built-in UI pattern predicates a component rendered in isolation can prove:
|
|
773
|
+
% the pattern, its markers and variant parity are visible in the rendered
|
|
774
|
+
% markup. Layout claims (ui_container, visual_layout_rule) depend on real
|
|
775
|
+
% rendering and keep the end-to-end requirement.
|
|
776
|
+
component_scope_predicate(ui_pattern).
|
|
777
|
+
component_scope_predicate(same_pattern).
|
|
778
|
+
component_scope_predicate(pattern_marker).
|
|
779
|
+
|
|
780
|
+
component_scope_scenario(ScenarioId) :-
|
|
781
|
+
findall(ReqId, kb_relationship(specified_by, ReqId, ScenarioId), ReqIds0),
|
|
782
|
+
sort(ReqIds0, ReqIds),
|
|
783
|
+
ReqIds \= [],
|
|
784
|
+
forall(member(ReqId, ReqIds), component_scope_requirement(ReqId)).
|
|
785
|
+
|
|
786
|
+
component_scope_requirement(ReqId) :-
|
|
787
|
+
findall(FactId, kb_relationship(requires_predicate, ReqId, FactId), FactIds),
|
|
788
|
+
FactIds \= [],
|
|
789
|
+
forall(member(FactId, FactIds),
|
|
790
|
+
( predicate_fact(FactId, _Namespace, Name, _Args, _Polarity),
|
|
791
|
+
component_scope_predicate(Name)
|
|
792
|
+
)),
|
|
793
|
+
\+ kb_relationship(requires_property, ReqId, _),
|
|
794
|
+
\+ kb_relationship(requires_rule, ReqId, _).
|
|
795
|
+
|
|
750
796
|
e2e_evidence(Item) :- Item.state \= not_end_to_end.
|
|
751
797
|
non_e2e_evidence(Item) :- Item.state == not_end_to_end.
|
|
752
798
|
|
|
@@ -799,9 +845,15 @@ evidence_tests_with_state(Evidence, State, TestIds) :-
|
|
|
799
845
|
sort(TestIds0, TestIds).
|
|
800
846
|
|
|
801
847
|
test_receipt_evidence(Context, TestId, Evidence) :-
|
|
848
|
+
test_receipt_evidence(Context, [end_to_end], TestId, Evidence).
|
|
849
|
+
|
|
850
|
+
% implements REQ-ui-pattern-component-proof
|
|
851
|
+
% AcceptedScopes lists the verification scopes whose receipts count for the
|
|
852
|
+
% scenario. Tests outside them keep the nonblocking not_end_to_end state.
|
|
853
|
+
test_receipt_evidence(Context, AcceptedScopes, TestId, Evidence) :-
|
|
802
854
|
kb_entity(TestId, test, Props),
|
|
803
855
|
test_scope(Props, Scope),
|
|
804
|
-
( Scope
|
|
856
|
+
( \+ memberchk(Scope, AcceptedScopes)
|
|
805
857
|
-> Evidence = _{testId: TestId, state: not_end_to_end, scope: Scope}
|
|
806
858
|
; Context.proofSnapshot == unknown
|
|
807
859
|
-> Evidence = _{testId: TestId, state: snapshot_unavailable, scope: Scope}
|
|
@@ -1216,7 +1268,7 @@ type_shape_symbol_with_structural_contract(SymbolId) :-
|
|
|
1216
1268
|
memberchk(Status, [active, passing]).
|
|
1217
1269
|
|
|
1218
1270
|
% Stage status plus a typed reason, from a single total clause so status and
|
|
1219
|
-
% reason can never disagree (
|
|
1271
|
+
% reason can never disagree (a test project's stale-receipts case reported `blocked`
|
|
1220
1272
|
% with no indication that fresh receipts were the unblock). Clause order keeps
|
|
1221
1273
|
% the historical precedence: no production symbols at all is `missing` even
|
|
1222
1274
|
% when E2E evidence is also absent; only then does absent E2E evidence read
|
package/src/rule_registry.pl
CHANGED
|
@@ -58,6 +58,8 @@ known_rule('adr-proposed').
|
|
|
58
58
|
known_rule('predicate-schema-conformance').
|
|
59
59
|
known_rule('entity-context-missing').
|
|
60
60
|
known_rule('entity-context-acknowledged').
|
|
61
|
+
known_rule('policy-ownership').
|
|
62
|
+
known_rule('policy-markers').
|
|
61
63
|
|
|
62
64
|
rule_enforcement_class('must-priority-coverage', canonical).
|
|
63
65
|
rule_enforcement_class('symbol-coverage', canonical).
|
|
@@ -103,6 +105,8 @@ rule_enforcement_class('adr-proposed', advisory).
|
|
|
103
105
|
rule_enforcement_class('predicate-schema-conformance', advisory).
|
|
104
106
|
rule_enforcement_class('entity-context-missing', canonical).
|
|
105
107
|
rule_enforcement_class('entity-context-acknowledged', advisory).
|
|
108
|
+
rule_enforcement_class('policy-ownership', canonical).
|
|
109
|
+
rule_enforcement_class('policy-markers', canonical).
|
|
106
110
|
|
|
107
111
|
rule_implementation('must-priority-coverage', prolog).
|
|
108
112
|
rule_implementation('symbol-coverage', prolog).
|
|
@@ -148,6 +152,8 @@ rule_implementation('adr-proposed', prolog).
|
|
|
148
152
|
rule_implementation('predicate-schema-conformance', typescript).
|
|
149
153
|
rule_implementation('entity-context-missing', typescript).
|
|
150
154
|
rule_implementation('entity-context-acknowledged', typescript).
|
|
155
|
+
rule_implementation('policy-ownership', typescript).
|
|
156
|
+
rule_implementation('policy-markers', typescript).
|
|
151
157
|
|
|
152
158
|
rule_predicate('must-priority-coverage', check_must_priority_coverage).
|
|
153
159
|
rule_predicate('symbol-coverage', check_symbol_coverage).
|
|
@@ -263,3 +269,5 @@ rule_description('adr-proposed', 'Informational: an ADR that is still proposed a
|
|
|
263
269
|
rule_description('predicate-schema-conformance', 'Predicate facts match a predicate_schema (project-local, or the built-in catalog in the default namespace) and use its declared argument constants').
|
|
264
270
|
rule_description('entity-context-missing', 'A current requirement, scenario, test, ADR or observation/meta fact must carry body context: at least 12 words of prose (requirements: under a Context, Rationale, Why, Background, Source, Notes or Evidence heading) that do not restate the title or semantic_text; entities tagged review:context-missing are acknowledged legacy and exempt').
|
|
265
271
|
rule_description('entity-context-acknowledged', 'Informational: how many current entities carry the review:context-missing tag, so the legacy context backlog stays visible; one summary finding per check').
|
|
272
|
+
rule_description('policy-ownership', 'When a plugin activated for kibi.check-policy.v1 declares ownership rules, every production symbol in a rule\'s file set must implement a current requirement grounded (requires_predicate) in one of the rule\'s predicates; projects without an activated check policy are unaffected').
|
|
273
|
+
rule_description('policy-markers', 'When a plugin activated for kibi.check-policy.v1 declares marker rules, every file implementing a requirement grounded in the rule\'s pattern predicate (with its declared sibling files) must contain each marker the KB declares for that pattern').
|