kibi-core 0.17.3 → 0.18.1
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 +24 -5
- package/src/kb.pl +31 -0
- package/src/requirement_proof.pl +55 -3
- package/src/rule_registry.pl +12 -0
- package/src/semantic_quality.pl +49 -8
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,10 +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),
|
|
464
|
-
|
|
483
|
+
(provenance_stub_props(Type, Props) -> Stub = true ; Stub = false),
|
|
465
484
|
matches_source(SourceFilter, Props),
|
|
466
485
|
relationships_missing(Id, MissingRelationships),
|
|
467
486
|
relationships_present(Id, PresentRelationships),
|
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
|
@@ -19,6 +19,7 @@ known_rule('symbol-coverage').
|
|
|
19
19
|
known_rule('symbol-traceability').
|
|
20
20
|
known_rule('no-dangling-refs').
|
|
21
21
|
known_rule('source-relationship-parity').
|
|
22
|
+
known_rule('branch-store-not-compiled').
|
|
22
23
|
known_rule('source-path-dangling').
|
|
23
24
|
known_rule('no-cycles').
|
|
24
25
|
known_rule('required-fields').
|
|
@@ -58,12 +59,15 @@ known_rule('adr-proposed').
|
|
|
58
59
|
known_rule('predicate-schema-conformance').
|
|
59
60
|
known_rule('entity-context-missing').
|
|
60
61
|
known_rule('entity-context-acknowledged').
|
|
62
|
+
known_rule('policy-ownership').
|
|
63
|
+
known_rule('policy-markers').
|
|
61
64
|
|
|
62
65
|
rule_enforcement_class('must-priority-coverage', canonical).
|
|
63
66
|
rule_enforcement_class('symbol-coverage', canonical).
|
|
64
67
|
rule_enforcement_class('symbol-traceability', canonical).
|
|
65
68
|
rule_enforcement_class('no-dangling-refs', canonical).
|
|
66
69
|
rule_enforcement_class('source-relationship-parity', canonical).
|
|
70
|
+
rule_enforcement_class('branch-store-not-compiled', canonical).
|
|
67
71
|
rule_enforcement_class('source-path-dangling', canonical).
|
|
68
72
|
rule_enforcement_class('no-cycles', canonical).
|
|
69
73
|
rule_enforcement_class('required-fields', canonical).
|
|
@@ -103,12 +107,15 @@ rule_enforcement_class('adr-proposed', advisory).
|
|
|
103
107
|
rule_enforcement_class('predicate-schema-conformance', advisory).
|
|
104
108
|
rule_enforcement_class('entity-context-missing', canonical).
|
|
105
109
|
rule_enforcement_class('entity-context-acknowledged', advisory).
|
|
110
|
+
rule_enforcement_class('policy-ownership', canonical).
|
|
111
|
+
rule_enforcement_class('policy-markers', canonical).
|
|
106
112
|
|
|
107
113
|
rule_implementation('must-priority-coverage', prolog).
|
|
108
114
|
rule_implementation('symbol-coverage', prolog).
|
|
109
115
|
rule_implementation('symbol-traceability', prolog).
|
|
110
116
|
rule_implementation('no-dangling-refs', prolog).
|
|
111
117
|
rule_implementation('source-relationship-parity', typescript).
|
|
118
|
+
rule_implementation('branch-store-not-compiled', typescript).
|
|
112
119
|
rule_implementation('source-path-dangling', typescript).
|
|
113
120
|
rule_implementation('no-cycles', prolog).
|
|
114
121
|
rule_implementation('required-fields', prolog).
|
|
@@ -148,6 +155,8 @@ rule_implementation('adr-proposed', prolog).
|
|
|
148
155
|
rule_implementation('predicate-schema-conformance', typescript).
|
|
149
156
|
rule_implementation('entity-context-missing', typescript).
|
|
150
157
|
rule_implementation('entity-context-acknowledged', typescript).
|
|
158
|
+
rule_implementation('policy-ownership', typescript).
|
|
159
|
+
rule_implementation('policy-markers', typescript).
|
|
151
160
|
|
|
152
161
|
rule_predicate('must-priority-coverage', check_must_priority_coverage).
|
|
153
162
|
rule_predicate('symbol-coverage', check_symbol_coverage).
|
|
@@ -224,6 +233,7 @@ rule_description('symbol-coverage', 'Production symbols need qualifying coverage
|
|
|
224
233
|
rule_description('symbol-traceability', 'Production symbols must directly implement requirements for ownership; covered_by is coverage only and executable_for is test identity only').
|
|
225
234
|
rule_description('no-dangling-refs', 'All relationship targets must exist in the KB').
|
|
226
235
|
rule_description('source-relationship-parity', 'Authored Markdown and relationship-shard edges must exactly match the compiled KB').
|
|
236
|
+
rule_description('branch-store-not-compiled', 'The current branch\'s KB store must be compiled from the authored .kb/ sources; a missing or empty store is reported once, naming kibi sync, instead of every store-backed rule reading an empty KB').
|
|
227
237
|
rule_description('source-path-dangling', 'An authored entity\'s source field must name an existing workspace path (a #anchor suffix is ignored), an existing entity id, or an http(s) URL; a missing source is allowed').
|
|
228
238
|
rule_description('no-cycles', 'No circular dependency chains in requirements').
|
|
229
239
|
rule_description('required-fields', 'All entities must have required fields').
|
|
@@ -263,3 +273,5 @@ rule_description('adr-proposed', 'Informational: an ADR that is still proposed a
|
|
|
263
273
|
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
274
|
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
275
|
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').
|
|
276
|
+
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').
|
|
277
|
+
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').
|
package/src/semantic_quality.pl
CHANGED
|
@@ -363,7 +363,10 @@ implication_witness_violation(Witness, violation(
|
|
|
363
363
|
% was minted as several facts so requirements about it never meet: more than
|
|
364
364
|
% one active subject fact for the same subject_key, and more than one active
|
|
365
365
|
% property_value fact stating the same claim: the same subject_key,
|
|
366
|
-
% property_key, operator, typed value and unit.
|
|
366
|
+
% property_key, operator, polarity (absent = require), typed value and unit.
|
|
367
|
+
% A `forbid` fact states the opposite of its `require` twin, so the pair is
|
|
368
|
+
% never one claim. Facts linked only by superseded or deprecated requirements
|
|
369
|
+
% are history, not live duplicates. Facts that differ in operator
|
|
367
370
|
% (the two bounds of a range) or in value (two requirements bounding the same
|
|
368
371
|
% property differently) are distinct claims, which domain-contradictions and
|
|
369
372
|
% domain-implication compare. Each group is reported once, on its first fact
|
|
@@ -390,7 +393,8 @@ duplicate_subject_fact_violations(Violations) :-
|
|
|
390
393
|
findall(
|
|
391
394
|
SubjectKey-FactId,
|
|
392
395
|
( fact_subject_key_of_kind(FactId, subject, SubjectKey),
|
|
393
|
-
active_fact(FactId)
|
|
396
|
+
active_fact(FactId),
|
|
397
|
+
identity_live_fact(FactId)
|
|
394
398
|
),
|
|
395
399
|
Pairs0
|
|
396
400
|
),
|
|
@@ -423,15 +427,17 @@ duplicate_subject_fact_violations(Violations) :-
|
|
|
423
427
|
%% duplicate_property_fact_violations(-Violations)
|
|
424
428
|
duplicate_property_fact_violations(Violations) :-
|
|
425
429
|
findall(
|
|
426
|
-
key(SubjectKey, PropertyKey, Operator, Value)-FactId,
|
|
430
|
+
key(SubjectKey, PropertyKey, Operator, Polarity, Value)-FactId,
|
|
427
431
|
( fact_subject_key_of_kind(FactId, property_value, SubjectKey),
|
|
428
432
|
kb_entity(FactId, fact, Props),
|
|
429
433
|
memberchk(property_key=RawProperty, Props),
|
|
430
434
|
normalize_term_atom(RawProperty, PropertyKey),
|
|
431
435
|
memberchk(operator=RawOperator, Props),
|
|
432
436
|
normalize_term_atom(RawOperator, Operator),
|
|
437
|
+
property_value_polarity(Props, Polarity),
|
|
433
438
|
property_value_signature(Props, Value),
|
|
434
|
-
active_fact(FactId)
|
|
439
|
+
active_fact(FactId),
|
|
440
|
+
identity_live_fact(FactId)
|
|
435
441
|
),
|
|
436
442
|
Pairs0
|
|
437
443
|
),
|
|
@@ -444,20 +450,55 @@ duplicate_property_fact_violations(Violations) :-
|
|
|
444
450
|
Description,
|
|
445
451
|
"Link every requirement to one shared fact and remove the duplicates (kb_delete) once nothing links them",
|
|
446
452
|
Source,
|
|
447
|
-
_{subjectKey: SubjectKey, propertyKey: PropertyKey, operator: Operator, facts: FactIds}
|
|
453
|
+
_{subjectKey: SubjectKey, propertyKey: PropertyKey, operator: Operator, polarity: Polarity, facts: FactIds}
|
|
448
454
|
),
|
|
449
|
-
( member(key(SubjectKey, PropertyKey, Operator, _)-FactIds, Groups),
|
|
455
|
+
( member(key(SubjectKey, PropertyKey, Operator, Polarity, _)-FactIds, Groups),
|
|
450
456
|
FactIds = [FirstId, _|_],
|
|
451
457
|
length(FactIds, Count),
|
|
452
458
|
atomic_list_concat(FactIds, ', ', FactText),
|
|
453
459
|
format(string(Description),
|
|
454
|
-
"~w active property_value facts state the same claim on ~w ~w (operator ~w, same value) (~w), so one claim is minted as several facts",
|
|
455
|
-
[Count, SubjectKey, PropertyKey, Operator, FactText]),
|
|
460
|
+
"~w active property_value facts state the same claim on ~w ~w (operator ~w, polarity ~w, same value) (~w), so one claim is minted as several facts",
|
|
461
|
+
[Count, SubjectKey, PropertyKey, Operator, Polarity, FactText]),
|
|
456
462
|
entity_text(FirstId, fact, source, Source)
|
|
457
463
|
),
|
|
458
464
|
Violations
|
|
459
465
|
).
|
|
460
466
|
|
|
467
|
+
%% property_value_polarity(+Props, -Polarity)
|
|
468
|
+
% The polarity of a property_value fact; an absent polarity means require.
|
|
469
|
+
% `forbid` states the opposite claim of `require` on the same tuple, so the
|
|
470
|
+
% two never count as one claim minted twice.
|
|
471
|
+
% implements REQ-kibi-subject-vocabulary
|
|
472
|
+
property_value_polarity(Props, Polarity) :-
|
|
473
|
+
( memberchk(polarity=Raw, Props),
|
|
474
|
+
normalize_term_atom(Raw, Polarity0),
|
|
475
|
+
Polarity0 \== ''
|
|
476
|
+
-> Polarity = Polarity0
|
|
477
|
+
; Polarity = require
|
|
478
|
+
).
|
|
479
|
+
|
|
480
|
+
%% identity_live_fact(+FactId)
|
|
481
|
+
% A fact still carried by the current requirement set: no requirement links
|
|
482
|
+
% it (an orphan duplicate stays visible), or at least one current requirement
|
|
483
|
+
% links it. A fact linked only by superseded or deprecated requirements is
|
|
484
|
+
% history kept for the append-only supersedes chain, not a live duplicate.
|
|
485
|
+
% `closed` means done, not retired, so a closed requirement still counts.
|
|
486
|
+
% implements REQ-kibi-subject-vocabulary
|
|
487
|
+
identity_live_fact(FactId) :-
|
|
488
|
+
findall(
|
|
489
|
+
ReqId,
|
|
490
|
+
( member(LinkType, [constrains, requires_property, requires_predicate, requires_rule]),
|
|
491
|
+
kb_relationship(LinkType, ReqId, FactId)
|
|
492
|
+
),
|
|
493
|
+
Linkers
|
|
494
|
+
),
|
|
495
|
+
( Linkers == []
|
|
496
|
+
-> true
|
|
497
|
+
; member(ReqId, Linkers),
|
|
498
|
+
kb:current_req(ReqId)
|
|
499
|
+
-> true
|
|
500
|
+
).
|
|
501
|
+
|
|
461
502
|
%% property_value_signature(+Props, -Signature)
|
|
462
503
|
% The typed value and unit of a property_value fact, as Field-Value pairs in a
|
|
463
504
|
% fixed field order, so two facts compare equal only when they store the same
|