kibi-core 0.8.1 → 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -29,8 +29,13 @@ entity_property(_, severity, atom).
29
29
  entity_property(_, links, list).
30
30
  entity_property(_, text_ref, uri).
31
31
  entity_property(_, sourceFile, uri).
32
+ entity_property(req, semantic_text, string).
32
33
  entity_property(req, logic_claims, list).
33
- entity_property(req, semantic_inventory, list).
34
+ entity_property(req, semantic_clauses, list).
35
+ entity_property(req, semantic_inventory_version, string).
36
+ entity_property(req, semantic_source_field, string).
37
+ entity_property(req, semantic_source_hash, string).
38
+ entity_property(req, semantic_inventory, list_or_json).
34
39
 
35
40
  % Typed fact fields - only valid for fact entities
36
41
  entity_property(fact, fact_kind, atom).
@@ -74,10 +79,15 @@ entity_property(fact, claim_span_end, integer).
74
79
  % Typed symbol metadata fields - only valid for symbol entities
75
80
  entity_property(symbol, symbol_role, atom).
76
81
  entity_property(symbol, granularity_reason, atom).
82
+ entity_property(symbol, sourceLine, integer).
83
+ entity_property(symbol, sourceColumn, integer).
84
+ entity_property(symbol, sourceEndLine, integer).
85
+ entity_property(symbol, sourceEndColumn, integer).
77
86
 
78
87
  % Typed test verification fields - only valid for test entities
79
88
  entity_property(test, verification_scope, atom).
80
89
  entity_property(test, verification_perspective, atom).
90
+ entity_property(test, verification_receipts, string).
81
91
 
82
92
  % Required properties for all entity types
83
93
  required_property(Type, id) :- entity_type(Type).
@@ -94,10 +104,16 @@ optional_property(Type, priority) :- entity_type(Type).
94
104
  optional_property(Type, severity) :- entity_type(Type).
95
105
  optional_property(Type, links) :- entity_type(Type).
96
106
  optional_property(Type, text_ref) :- entity_type(Type).
107
+ optional_property(req, semantic_text).
97
108
  optional_property(req, logic_claims).
109
+ optional_property(req, semantic_clauses).
110
+ optional_property(req, semantic_inventory_version).
111
+ optional_property(req, semantic_source_field).
112
+ optional_property(req, semantic_source_hash).
98
113
  optional_property(req, semantic_inventory).
99
114
  optional_property(test, verification_scope).
100
115
  optional_property(test, verification_perspective).
116
+ optional_property(test, verification_receipts).
101
117
 
102
118
  % Documentation helpers
103
119
  % list all entity types
@@ -45,6 +45,7 @@ check_kind(string, V) :- string(V).
45
45
  check_kind(atom_or_string, V) :- atom(V) ; string(V).
46
46
  check_kind(datetime, V) :- string(V). % accept ISO strings for now
47
47
  check_kind(list, V) :- is_list(V).
48
+ check_kind(list_or_json, V) :- is_list(V) ; string(V) ; atom(V).
48
49
  check_kind(uri, V) :- string(V).
49
50
  check_kind(integer, V) :- integer(V).
50
51
  check_kind(number, V) :- number(V).
@@ -118,6 +119,7 @@ is_fact_only_field(claim_span_end).
118
119
  % is_test_only_field(+Key) - true if Key is a test-specific field
119
120
  is_test_only_field(verification_scope).
120
121
  is_test_only_field(verification_perspective).
122
+ is_test_only_field(verification_receipts).
121
123
 
122
124
  % validate_fact_shape(+Kind, +Props)
123
125
  validate_fact_shape(subject, Props) :-
package/src/checks.pl CHANGED
@@ -14,6 +14,7 @@
14
14
  check_required_fields/1, % Returns list of missing required field violations
15
15
  check_deprecated_adrs/1, % Returns list of deprecated ADR violations
16
16
  check_domain_contradictions/1, % Returns list of contradiction violations
17
+ check_domain_contradiction_witnesses/1,
17
18
  check_strict_fact_shape/1, % Returns list of malformed strict fact violations
18
19
  check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
19
20
  check_strict_readiness/1, % Returns list of strict readiness audit violations
@@ -31,7 +32,7 @@
31
32
  :- use_module('kb.pl').
32
33
  :- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
33
34
  :- use_module('../schema/relationships.pl', [relationship_type/1]).
34
- :- use_module('logic_ir.pl', [logic_rule_safety/2, logic_rule_from_props/2, logic_rule_conflict/3, logic_rules_stratified/1]).
35
+ :- use_module('logic_ir.pl', [logic_rule_safety/2, logic_rule_from_props/2, logic_rule_conflict/3, logic_rule_conflict_witness/3, logic_rules_stratified/1]).
35
36
 
36
37
  % Required fields for all entities
37
38
  required_fields([id, title, status, created_at, updated_at, source]).
@@ -587,39 +588,39 @@ checks_normalize_atom_list(Raw, Atoms) :-
587
588
  %% check_domain_contradictions(-Violations)
588
589
  % Finds all pairs of requirements with contradicting required properties.
589
590
  check_domain_contradictions(Violations) :-
590
- findall(
591
- violation(
592
- 'domain-contradictions',
593
- EntityId,
594
- Description,
595
- "Supersede one requirement or align both to the same required property",
596
- ""
597
- ),
598
- ( contradicting_reqs(ReqA, ReqB, Reason),
599
- format(string(EntityId), "~w/~w", [ReqA, ReqB]),
600
- format(string(Description), "~w [strict-readiness: contradiction-ready]", [Reason])
601
- ),
602
- StrictViolations
603
- ),
604
- findall(
605
- violation(
606
- 'domain-contradictions',
607
- EntityId,
608
- Description,
609
- "Align the opposing rule heads, separate their scopes, or add an explicit supersedes relationship",
610
- ""
611
- ),
612
- ( opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB),
613
- logic_rule_conflict(RuleA, RuleB, Conflict),
614
- Conflict \= disjoint,
615
- format(string(EntityId), "~w/~w", [ReqA, ReqB]),
616
- format(string(Description), "Rule conflict (~w) between ~w and ~w", [Conflict, ReqA, ReqB])
617
- ),
618
- RuleViolations
619
- ),
620
- append(StrictViolations, RuleViolations, Violations).
591
+ check_domain_contradiction_witnesses(Witnesses),
592
+ maplist(contradiction_witness_violation, Witnesses, Violations).
593
+
594
+ %% check_domain_contradiction_witnesses(-Witnesses)
595
+ % Exact, JSON-safe evidence for every strict-property, ground-predicate, and
596
+ % safe-rule contradiction result. Rule overlap that cannot be proven or
597
+ % disproven remains status=unresolved rather than becoming consistency.
598
+ check_domain_contradiction_witnesses(Witnesses) :-
599
+ findall(Witness, req_conflict_witness(_ReqA, _ReqB, Witness), GroundWitnesses),
600
+ findall(Witness, rule_contradiction_witness(Witness), RuleWitnesses),
601
+ append(GroundWitnesses, RuleWitnesses, Witnesses0),
602
+ sort(Witnesses0, Witnesses).
603
+
604
+ contradiction_witness_violation(Witness, violation(
605
+ 'domain-contradictions',
606
+ EntityId,
607
+ Description,
608
+ Suggestion,
609
+ ""
610
+ )) :-
611
+ Witness.requirements = [ReqA, ReqB],
612
+ format(string(EntityId), "~w/~w", [ReqA, ReqB]),
613
+ ( Witness.kind == rule
614
+ -> Description = Witness.reason,
615
+ Suggestion = "Align the opposing rule heads, separate their scopes, or add an explicit supersedes relationship"
616
+ ; format(string(Description), "~w [strict-readiness: contradiction-ready]", [Witness.reason]),
617
+ Suggestion = "Supersede one requirement or align both to the same canonical logical term"
618
+ ).
621
619
 
622
620
  opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB) :-
621
+ opposing_rule_requirement_facts(ReqA, ReqB, _FactA, _FactB, RuleA, RuleB).
622
+
623
+ opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB) :-
623
624
  kb:current_req(ReqA),
624
625
  kb:current_req(ReqB),
625
626
  ReqA @< ReqB,
@@ -635,6 +636,94 @@ opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB) :-
635
636
  logic_rule_from_props(PropsA, RuleA),
636
637
  logic_rule_from_props(PropsB, RuleB).
637
638
 
639
+ rule_contradiction_witness(Witness) :-
640
+ opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB),
641
+ logic_rule_conflict_witness(RuleA, RuleB, InternalWitness),
642
+ InternalWitness.status \= disjoint,
643
+ rule_conflict_side(ReqA, FactA, Left),
644
+ rule_conflict_side(ReqB, FactB, Right),
645
+ rule_comparison_evidence(InternalWitness, Comparison),
646
+ format(string(Reason), "Rule conflict (~w) between ~w and ~w", [InternalWitness.status, ReqA, ReqB]),
647
+ Witness = _{
648
+ kind: rule,
649
+ status: InternalWitness.status,
650
+ requirements: [ReqA, ReqB],
651
+ reason: Reason,
652
+ left: Left,
653
+ right: Right,
654
+ comparison: Comparison
655
+ }.
656
+
657
+ rule_conflict_side(ReqId, FactId, Side) :-
658
+ evidence_entity_source(ReqId, req, RequirementSource),
659
+ kb_entity(FactId, fact, Props),
660
+ evidence_property_text(Props, source, FactSource),
661
+ evidence_property_text(Props, claim_key, ClaimKey),
662
+ evidence_property_text(Props, claim_text, ClaimText),
663
+ evidence_property_text(Props, rule_hash, RuleHash),
664
+ evidence_property_text(Props, semantic_key, SemanticKey),
665
+ evidence_property_text(Props, rule_ir, RuleIr),
666
+ evidence_property_integer(Props, claim_span_start, ClaimSpanStart),
667
+ evidence_property_integer(Props, claim_span_end, ClaimSpanEnd),
668
+ Side = _{
669
+ requirementId: ReqId,
670
+ requirementSource: RequirementSource,
671
+ factId: FactId,
672
+ factSource: FactSource,
673
+ claimKey: ClaimKey,
674
+ claimText: ClaimText,
675
+ claimSpan: _{start: ClaimSpanStart, end: ClaimSpanEnd},
676
+ ruleHash: RuleHash,
677
+ semanticKey: SemanticKey,
678
+ ruleIr: RuleIr
679
+ }.
680
+
681
+ rule_comparison_evidence(Internal, Evidence) :-
682
+ term_string(Internal.head_a, HeadA, [quoted(true), max_depth(0)]),
683
+ term_string(Internal.head_b, HeadB, [quoted(true), max_depth(0)]),
684
+ term_string(Internal.body_a, BodyA, [quoted(true), max_depth(0)]),
685
+ term_string(Internal.body_b, BodyB, [quoted(true), max_depth(0)]),
686
+ term_string(Internal.scope_a, ScopeA, [quoted(true), max_depth(0)]),
687
+ term_string(Internal.scope_b, ScopeB, [quoted(true), max_depth(0)]),
688
+ Evidence = _{
689
+ modalityA: Internal.modality_a,
690
+ modalityB: Internal.modality_b,
691
+ headA: HeadA,
692
+ headB: HeadB,
693
+ bodyA: BodyA,
694
+ bodyB: BodyB,
695
+ scopeA: ScopeA,
696
+ scopeB: ScopeB,
697
+ validFromA: Internal.valid_from_a,
698
+ validToA: Internal.valid_to_a,
699
+ validFromB: Internal.valid_from_b,
700
+ validToB: Internal.valid_to_b,
701
+ ruleSchemaA: Internal.rule_schema_a,
702
+ ruleSchemaB: Internal.rule_schema_b
703
+ }.
704
+
705
+ evidence_entity_source(EntityId, Type, Source) :-
706
+ kb_entity(EntityId, Type, Props),
707
+ evidence_property_text(Props, source, Source).
708
+
709
+ evidence_property_text(Props, Key, Text) :-
710
+ (memberchk(Key=Raw, Props) -> evidence_term_text(Raw, Text) ; Text = '').
711
+
712
+ evidence_property_integer(Props, Key, Integer) :-
713
+ (memberchk(Key=Raw, Props), catch(checks_normalize_integer(Raw, Integer), _, fail) -> true ; Integer = -1).
714
+
715
+ evidence_term_text(Raw, Text) :-
716
+ ( Raw = ^^(Value, _Type)
717
+ -> evidence_term_text(Value, Text)
718
+ ; Raw = literal(type(_, Value))
719
+ -> evidence_term_text(Value, Text)
720
+ ; atom(Raw)
721
+ -> Text = Raw
722
+ ; string(Raw)
723
+ -> atom_string(Text, Raw)
724
+ ; term_string(Raw, String), atom_string(Text, String)
725
+ ).
726
+
638
727
  %% check_strict_req_fact_pairing(-Violations)
639
728
  % Finds current requirements attempting strict-lane modeling with incomplete
640
729
  % subject/property pairing or wrong-lane fact targets.
@@ -1194,7 +1283,7 @@ inventory_entries(Raw, Entries) :-
1194
1283
  ( is_list(Raw) -> Entries = Raw
1195
1284
  ; Raw = ^^(Value, _) -> inventory_entries(Value, Entries)
1196
1285
  ; (atom(Raw) ; string(Raw)),
1197
- catch(atom_json_term(Raw, JsonEntries, [value_string_as(string)]), _, fail),
1286
+ catch(atom_json_dict(Raw, JsonEntries, [value_string_as(string)]), _, fail),
1198
1287
  is_list(JsonEntries)
1199
1288
  -> Entries = JsonEntries
1200
1289
  ; Entries = []
@@ -1319,7 +1408,28 @@ pairs_to_json_pairs([Key-Violations|Rest], [Key-JsonViolations|JsonRest]) :-
1319
1408
  % Converts a violation(Rule, EntityId, Description, Suggestion, Source) term
1320
1409
  % to a JSON-compatible dict.
1321
1410
  violation_to_json(Violation, JsonDict) :-
1322
- violation_term_to_dict(Violation, JsonDict).
1411
+ violation_term_to_dict(Violation, BaseDict),
1412
+ ( contradiction_violation_witnesses(Violation, Witnesses),
1413
+ Witnesses \= []
1414
+ -> put_dict(evidence, BaseDict, _{witnesses: Witnesses}, JsonDict)
1415
+ ; JsonDict = BaseDict
1416
+ ).
1417
+
1418
+ contradiction_violation_witnesses(
1419
+ violation('domain-contradictions', EntityId, Description, _Suggestion, _Source),
1420
+ Witnesses
1421
+ ) :-
1422
+ violation_id_text(EntityId, EntityIdText),
1423
+ violation_text(Description, DescriptionText),
1424
+ check_domain_contradiction_witnesses(AllWitnesses),
1425
+ include(witness_matches_violation(EntityIdText, DescriptionText), AllWitnesses, Witnesses).
1426
+
1427
+ witness_matches_violation(EntityIdText, DescriptionText, Witness) :-
1428
+ Witness.requirements = [ReqA, ReqB],
1429
+ format(string(ExpectedEntityId), "~w/~w", [ReqA, ReqB]),
1430
+ ExpectedEntityId == EntityIdText,
1431
+ violation_text(Witness.reason, ReasonText),
1432
+ sub_string(DescriptionText, 0, _, _, ReasonText).
1323
1433
 
1324
1434
  violation_term_to_dict(violation(Rule, EntityId, Description, Suggestion, Source), JsonDict) :-
1325
1435
  violation_text(Rule, RuleText),
package/src/discovery.pl CHANGED
@@ -4,12 +4,14 @@
4
4
  :- module(discovery, [
5
5
  find_gaps_json/8,
6
6
  coverage_report_json/7,
7
+ coverage_report_json/10,
7
8
  graph_expand_json/8
8
9
  ]).
9
10
 
10
11
  :- use_module(library(http/json)).
11
12
  :- use_module(library(aggregate)).
12
13
  :- use_module('kb.pl').
14
+ :- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof/4]).
13
15
  :- use_module('status.pl', [status_meta_dict/1]).
14
16
  :- use_module('../schema/relationships.pl', [relationship_type/1]).
15
17
 
@@ -27,7 +29,10 @@ find_gaps_json(TypeFilter, MissingRelationships, PresentRelationships, Tags, Sou
27
29
  dict_json_string(Response, JsonString).
28
30
 
29
31
  coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, JsonString) :-
30
- coverage_rows(By, Tags, IncludePassing, IncludeTransitive, Rows0, Summary),
32
+ coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, unknown, '1970-01-01T00:00:00Z', 604800, JsonString).
33
+
34
+ coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
35
+ coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary),
31
36
  sort_dict_rows(Rows0, SortedRows),
32
37
  paginate_rows(SortedRows, Offset, Limit, Rows),
33
38
  status_meta_dict(Meta),
@@ -95,14 +100,15 @@ relationship_count(Id, Relationship, Count) :-
95
100
  (kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
96
101
  Count).
97
102
 
98
- coverage_rows(req, Tags, IncludePassing, IncludeTransitive, Rows, Summary) :-
103
+ coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
99
104
  !,
105
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
100
106
  findall(Row,
101
- requirement_coverage_row(Tags, IncludeTransitive, Row),
107
+ requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row),
102
108
  AllRows),
103
109
  filter_req_coverage_rows(IncludePassing, AllRows, Rows),
104
110
  coverage_summary(AllRows, Summary).
105
- coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, Rows, Summary) :-
111
+ coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, _VerificationSnapshot, _CheckedAt, _MaxAgeSeconds, Rows, Summary) :-
106
112
  !,
107
113
  findall(Row,
108
114
  symbol_coverage_row(Tags, Row),
@@ -110,10 +116,23 @@ coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, Rows, Summary) :
110
116
  filter_symbol_coverage_rows(IncludePassing, AllRows, Rows),
111
117
  length(AllRows, Total),
112
118
  include(symbol_row_fully_covered, AllRows, CoveredRows),
119
+ include(symbol_row_uncovered, AllRows, UncoveredRows),
120
+ include(symbol_row_not_applicable, AllRows, NotApplicableRows),
121
+ include(symbol_row_mixed_role, AllRows, MixedRoleRows),
113
122
  length(CoveredRows, Covered),
114
- Uncovered is Total - Covered,
115
- Summary = _{total: Total, fullyCovered: Covered, uncovered: Uncovered}.
116
- coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, Rows, Summary) :-
123
+ length(UncoveredRows, Uncovered),
124
+ length(NotApplicableRows, NotApplicable),
125
+ length(MixedRoleRows, MixedRole),
126
+ Applicable is Total - NotApplicable,
127
+ Summary = _{
128
+ total: Total,
129
+ applicable: Applicable,
130
+ fullyCovered: Covered,
131
+ uncovered: Uncovered,
132
+ notApplicable: NotApplicable,
133
+ mixedRole: MixedRole
134
+ }.
135
+ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, _VerificationSnapshot, _CheckedAt, _MaxAgeSeconds, Rows, Summary) :-
117
136
  findall(Type-Count,
118
137
  type_entity_count(Type, Count),
119
138
  Pairs),
@@ -121,7 +140,7 @@ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, Rows, Summary) :
121
140
  length(Rows, Total),
122
141
  Summary = _{total: Total}.
123
142
 
124
- requirement_coverage_row(Tags, IncludeTransitive, Row) :-
143
+ requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row) :-
125
144
  kb_entity(Id, req, Props),
126
145
  matches_tags(Tags, Props),
127
146
  entity_title_status_source(Props, Title, Status, _Source),
@@ -132,7 +151,7 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
132
151
  count_transitive_symbols(Id, IncludeTransitive, TransitiveSymbolCount),
133
152
  requirement_coverage_state(Id, Props, Gaps, Evaluated, CoverageStatus),
134
153
  requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, CoverageEvidence),
135
- Row = _{
154
+ BaseRow = _{
136
155
  id: Id,
137
156
  type: req,
138
157
  title: Title,
@@ -151,7 +170,9 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
151
170
  scenarioTests: CoverageEvidence.scenarioTests,
152
171
  testStatuses: CoverageEvidence.testStatuses,
153
172
  verificationScopes: CoverageEvidence.verificationScopes
154
- }.
173
+ },
174
+ requirement_proof(Id, Props, ProofContext, Proof),
175
+ put_dict(Proof, BaseRow, Row).
155
176
 
156
177
  symbol_coverage_row(Tags, Row) :-
157
178
  kb_entity(Id, symbol, Props),
@@ -159,19 +180,32 @@ symbol_coverage_row(Tags, Row) :-
159
180
  entity_title_status_source(Props, Title, Status, _Source),
160
181
  count_direct_requirements(Id, DirectRequirementCount),
161
182
  count_direct_tests(Id, TestCount),
162
- ( symbol_no_req_coverage(Id, _)
163
- -> Gaps = [missing_requirement],
183
+ count_executable_tests(Id, ExecutableTestCount),
184
+ ( mixed_role_symbol(Id)
185
+ -> TraceabilityRole = mixed,
186
+ Gaps = [invalid_mixed_symbol_role],
164
187
  CoverageStatus = uncovered
165
- ; Gaps = [],
166
- CoverageStatus = fully_covered
188
+ ; executable_test_symbol(Id)
189
+ -> TraceabilityRole = executable_test,
190
+ Gaps = [],
191
+ CoverageStatus = not_applicable
192
+ ; TraceabilityRole = production,
193
+ ( symbol_no_req_coverage(Id, _)
194
+ -> Gaps = [missing_requirement],
195
+ CoverageStatus = uncovered
196
+ ; Gaps = [],
197
+ CoverageStatus = fully_covered
198
+ )
167
199
  ),
168
200
  Row = _{
169
201
  id: Id,
170
202
  type: symbol,
171
203
  title: Title,
172
204
  status: Status,
205
+ traceabilityRole: TraceabilityRole,
173
206
  directRequirementCount: DirectRequirementCount,
174
207
  testCount: TestCount,
208
+ executableTestCount: ExecutableTestCount,
175
209
  gaps: Gaps,
176
210
  coverageStatus: CoverageStatus
177
211
  }.
@@ -185,6 +219,10 @@ coverage_summary(Rows, Summary) :-
185
219
  include(req_row_missing_scenario, Rows, MissingScenarioRows),
186
220
  include(req_row_missing_test, Rows, MissingTestRows),
187
221
  include(req_row_missing_both, Rows, MissingBothRows),
222
+ include(req_row_proof_proven, Rows, ProofProvenRows),
223
+ include(req_row_proof_unresolved, Rows, ProofUnresolvedRows),
224
+ include(req_row_proof_missing, Rows, ProofMissingRows),
225
+ include(req_row_proof_not_applicable, Rows, ProofNotApplicableRows),
188
226
  length(EvaluatedRows, Evaluated),
189
227
  length(FullyCoveredRows, FullyCovered),
190
228
  length(UncoveredRows, Uncovered),
@@ -192,6 +230,10 @@ coverage_summary(Rows, Summary) :-
192
230
  length(MissingScenarioRows, MissingScenario),
193
231
  length(MissingTestRows, MissingTest),
194
232
  length(MissingBothRows, MissingScenarioAndTest),
233
+ length(ProofProvenRows, ProofProven),
234
+ length(ProofUnresolvedRows, ProofUnresolved),
235
+ length(ProofMissingRows, ProofMissing),
236
+ length(ProofNotApplicableRows, ProofNotApplicable),
195
237
  Summary = _{
196
238
  total: Total,
197
239
  evaluated: Evaluated,
@@ -200,7 +242,11 @@ coverage_summary(Rows, Summary) :-
200
242
  notApplicable: NotApplicable,
201
243
  missingScenario: MissingScenario,
202
244
  missingTest: MissingTest,
203
- missingScenarioAndTest: MissingScenarioAndTest
245
+ missingScenarioAndTest: MissingScenarioAndTest,
246
+ proofProven: ProofProven,
247
+ proofUnresolved: ProofUnresolved,
248
+ proofMissing: ProofMissing,
249
+ proofNotApplicable: ProofNotApplicable
204
250
  }.
205
251
 
206
252
  req_row_fully_covered(Row) :-
@@ -221,10 +267,24 @@ req_row_missing_test(Row) :-
221
267
  Row.gaps = [missing_test].
222
268
  req_row_missing_both(Row) :-
223
269
  Row.gaps = [missing_scenario_and_test].
270
+ req_row_proof_proven(Row) :-
271
+ Row.proofStatus == proven.
272
+ req_row_proof_unresolved(Row) :-
273
+ Row.proofStatus == unresolved.
274
+ req_row_proof_missing(Row) :-
275
+ Row.proofStatus == missing.
276
+ req_row_proof_not_applicable(Row) :-
277
+ Row.proofStatus == not_applicable.
224
278
 
225
279
  symbol_row_fully_covered(Row) :-
226
280
  CoverageStatus = Row.get(coverageStatus),
227
281
  CoverageStatus = fully_covered.
282
+ symbol_row_uncovered(Row) :-
283
+ Row.coverageStatus == uncovered.
284
+ symbol_row_not_applicable(Row) :-
285
+ Row.coverageStatus == not_applicable.
286
+ symbol_row_mixed_role(Row) :-
287
+ Row.traceabilityRole == mixed.
228
288
 
229
289
  count_distinct_targets(Relationship, Id, source, Count) :-
230
290
  !,
@@ -235,7 +295,8 @@ count_distinct_targets(Relationship, Id, source, Count) :-
235
295
  requirement_test_count(Id, Count) :-
236
296
  findall(TestId, kb_relationship(verified_by, Id, TestId), Verified0),
237
297
  findall(TestId, kb_relationship(validates, TestId, Id), Validates0),
238
- append(Verified0, Validates0, Combined0),
298
+ findall(TestId, scenario_requirement_test(Id, TestId), ScenarioTests0),
299
+ append([Verified0, Validates0, ScenarioTests0], Combined0),
239
300
  sort(Combined0, Combined),
240
301
  length(Combined, Count).
241
302
 
@@ -381,6 +442,11 @@ count_direct_tests(Id, Count) :-
381
442
  sort(TestIds0, TestIds),
382
443
  length(TestIds, Count).
383
444
 
445
+ count_executable_tests(Id, Count) :-
446
+ findall(TestId, kb_relationship(executable_for, Id, TestId), TestIds0),
447
+ sort(TestIds0, TestIds),
448
+ length(TestIds, Count).
449
+
384
450
  requirement_gap_list(Id, [Reason]) :-
385
451
  coverage_gap(Id, Reason),
386
452
  !.
@@ -399,11 +465,15 @@ requirement_coverage_state(_Id, _Props, [], false, not_applicable).
399
465
 
400
466
  filter_req_coverage_rows(true, Rows, Rows).
401
467
  filter_req_coverage_rows(false, Rows, Filtered) :-
402
- exclude(req_row_fully_covered, Rows, Filtered).
468
+ exclude(req_row_proof_passing, Rows, Filtered).
469
+
470
+ req_row_proof_passing(Row) :-
471
+ Status = Row.proofStatus,
472
+ memberchk(Status, [proven, not_applicable]).
403
473
 
404
474
  filter_symbol_coverage_rows(true, Rows, Rows).
405
475
  filter_symbol_coverage_rows(false, Rows, Filtered) :-
406
- exclude(symbol_row_fully_covered, Rows, Filtered).
476
+ include(symbol_row_uncovered, Rows, Filtered).
407
477
 
408
478
  entity_priority(Props, Priority) :-
409
479
  ( memberchk(priority=PriorityValue, Props)