kibi-core 0.12.0 → 0.13.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.12.0",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -8,10 +8,7 @@
8
8
  "node": ">=18"
9
9
  },
10
10
  "main": "./src/kb.pl",
11
- "files": [
12
- "src/**/*.pl",
13
- "schema/**/*.pl"
14
- ],
11
+ "files": ["src/**/*.pl", "schema/**/*.pl"],
15
12
  "license": "AGPL-3.0-or-later",
16
13
  "author": "Piotr Franczyk",
17
14
  "repository": {
package/src/checks.pl CHANGED
@@ -10,6 +10,7 @@
10
10
  check_must_priority_coverage/1, % Returns list of must-priority violations
11
11
  check_symbol_coverage/1, % Returns list of uncovered symbols
12
12
  check_symbol_traceability/2, % Returns list of symbols lacking requirement traceability (ReqAdr option)
13
+ check_proof_contract_symbols/1, % Returns unresolved or unexpected required_proofs symbol issues
13
14
  check_no_dangling_refs/1, % Returns list of dangling ref violations
14
15
  check_no_cycles/1, % Returns list of cycle violations
15
16
  check_required_fields/1, % Returns list of missing required field violations
@@ -70,6 +71,7 @@ check_all(ViolationsDict) :-
70
71
  check_rule_verifiability(RuleVerifiability),
71
72
  check_semantic_completeness(SemanticCompleteness),
72
73
  check_req_status_vocabulary(ReqStatusVocabulary),
74
+ check_proof_contract_symbols(ProofContractSymbols),
73
75
  ViolationsDict = _{
74
76
  must_priority_coverage: MustPriority,
75
77
  symbol_coverage: SymbolCoverage,
@@ -87,7 +89,8 @@ check_all(ViolationsDict) :-
87
89
  rule_safety: RuleSafety,
88
90
  rule_verifiability: RuleVerifiability,
89
91
  semantic_completeness: SemanticCompleteness,
90
- req_status_vocabulary: ReqStatusVocabulary
92
+ req_status_vocabulary: ReqStatusVocabulary,
93
+ proof_contract_symbols: ProofContractSymbols
91
94
  }.
92
95
 
93
96
  %% check_must_priority_coverage(-Violations)
@@ -148,6 +151,17 @@ check_symbol_traceability(RequireAdr, Violations) :-
148
151
  ),
149
152
  sort(Violations0, Violations).
150
153
 
154
+ symbol_traceability_violation(_RequireAdr, violation(
155
+ 'symbol-traceability',
156
+ SymbolId,
157
+ "Symbol mixes executable_for test identity with production implements or covered_by.",
158
+ "Split the symbol: keep executable_for on the test-code symbol and use a separate production symbol for implements/covered_by.",
159
+ Source
160
+ )) :-
161
+ kb_entity(SymbolId, symbol, _),
162
+ mixed_role_symbol(SymbolId),
163
+ violation_source(SymbolId, symbol, Source).
164
+
151
165
  symbol_traceability_violation(RequireAdr, violation(
152
166
  'symbol-traceability',
153
167
  SymbolId,
@@ -186,6 +200,118 @@ symbol_traceability_violation(RequireAdr, violation(
186
200
  ),
187
201
  violation_source(SymbolId, symbol, Source).
188
202
 
203
+ %% check_proof_contract_symbols(-Violations)
204
+ % Advisory integrity for proof_contract.required_proofs and optional
205
+ % proof_bindings.source_file. Kibi does not infer TEST names from filenames.
206
+ check_proof_contract_symbols(Violations) :-
207
+ findall(Violation, proof_contract_symbol_violation(Violation), Violations0),
208
+ sort(Violations0, Violations).
209
+
210
+ proof_contract_symbol_violation(violation(
211
+ 'proof-contract-symbols',
212
+ TestId,
213
+ Description,
214
+ Suggestion,
215
+ Source
216
+ )) :-
217
+ kb_entity(TestId, test, Props),
218
+ memberchk(proof_contract=RawContract, Props),
219
+ parse_json_object(RawContract, Contract),
220
+ required_proof_symbol_id(Contract, SymbolId),
221
+ \+ kb_entity(SymbolId, symbol, _),
222
+ format(string(Description), "proof_contract.required_proofs names unresolved symbol ~w", [SymbolId]),
223
+ Suggestion = "Point required_proofs.symbol_id at an existing symbol entity; Kibi does not infer TEST names from source filenames.",
224
+ violation_source(TestId, test, Source).
225
+
226
+ proof_contract_symbol_violation(violation(
227
+ 'proof-contract-symbols',
228
+ TestId,
229
+ Description,
230
+ Suggestion,
231
+ Source
232
+ )) :-
233
+ kb_entity(TestId, test, Props),
234
+ memberchk(proof_contract=RawContract, Props),
235
+ parse_json_object(RawContract, Contract),
236
+ required_proof_symbol_id(Contract, SymbolId),
237
+ kb_entity(SymbolId, symbol, SymbolProps),
238
+ memberchk(symbol_role=RawRole, SymbolProps),
239
+ normalize_term_atom(RawRole, 'type-shape'),
240
+ format(string(Description), "proof_contract.required_proofs names type-shape symbol ~w; required proofs must not target structural contracts", [SymbolId]),
241
+ Suggestion = "Required proofs identify executable proof symbols. Use a production or executable-test symbol, not a type-shape contract.",
242
+ violation_source(TestId, test, Source).
243
+
244
+ proof_contract_symbol_violation(violation(
245
+ 'proof-contract-symbols',
246
+ TestId,
247
+ Description,
248
+ Suggestion,
249
+ Source
250
+ )) :-
251
+ kb_entity(TestId, test, Props),
252
+ memberchk(proof_bindings=RawBindings, Props),
253
+ parse_json_list(RawBindings, Bindings),
254
+ member(Binding, Bindings),
255
+ is_dict(Binding),
256
+ (get_dict(source_file, Binding, RawBindingSource) ; get_dict(sourceFile, Binding, RawBindingSource)),
257
+ normalize_term_atom(RawBindingSource, BindingSource),
258
+ BindingSource \= '',
259
+ (get_dict(symbol_id, Binding, RawSymbolId) ; get_dict(symbolId, Binding, RawSymbolId)),
260
+ normalize_term_atom(RawSymbolId, SymbolId),
261
+ SymbolId \= '',
262
+ kb_entity(SymbolId, symbol, SymbolProps),
263
+ ( memberchk(sourceFile=RawSymbolSource, SymbolProps)
264
+ -> normalize_term_atom(RawSymbolSource, SymbolSource)
265
+ ; SymbolSource = ''
266
+ ),
267
+ SymbolSource \= BindingSource,
268
+ format(string(Description), "proof_bindings.source_file for ~w is ~w but the symbol sourceFile is ~w", [SymbolId, BindingSource, SymbolSource]),
269
+ Suggestion = "Align proof_bindings.source_file with the symbol's sourceFile, or omit source_file. Kibi does not infer TEST names from filenames.",
270
+ violation_source(TestId, test, Source).
271
+
272
+ required_proof_symbol_id(Contract, SymbolId) :-
273
+ ( get_dict(required_proofs, Contract, Raw)
274
+ ; get_dict('required_proofs', Contract, Raw)
275
+ ),
276
+ parse_json_list(Raw, Entries),
277
+ member(Entry, Entries),
278
+ ( is_dict(Entry),
279
+ (get_dict(symbol_id, Entry, RawId) ; get_dict(symbolId, Entry, RawId))
280
+ ; is_list(Entry),
281
+ memberchk(symbol_id=RawId, Entry)
282
+ ),
283
+ normalize_term_atom(RawId, SymbolId),
284
+ SymbolId \= ''.
285
+
286
+ parse_json_object(^^(Value, _), Dict) :-
287
+ !,
288
+ parse_json_object(Value, Dict).
289
+ parse_json_object(literal(type(_, Value)), Dict) :-
290
+ !,
291
+ parse_json_object(Value, Dict).
292
+ parse_json_object(literal(Value), Dict) :-
293
+ !,
294
+ parse_json_object(Value, Dict).
295
+ parse_json_object(Dict, Dict) :-
296
+ is_dict(Dict),
297
+ !.
298
+ parse_json_object(Raw, Dict) :-
299
+ (atom(Raw) ; string(Raw)),
300
+ catch(atom_json_dict(Raw, Dict, [value_string_as(string)]), _, fail),
301
+ is_dict(Dict).
302
+
303
+ parse_json_list(Raw, Entries) :-
304
+ is_list(Raw),
305
+ !,
306
+ Entries = Raw.
307
+ parse_json_list(^^(Value, _), Entries) :-
308
+ !,
309
+ parse_json_list(Value, Entries).
310
+ parse_json_list(Raw, Entries) :-
311
+ (atom(Raw) ; string(Raw)),
312
+ catch(atom_json_dict(Raw, Entries, [value_string_as(string)]), _, fail),
313
+ is_list(Entries).
314
+
189
315
  %% check_no_dangling_refs(-Violations)
190
316
  % Finds all relationships referencing non-existent entities.
191
317
  check_no_dangling_refs(Violations) :-
@@ -1478,7 +1604,8 @@ check_selected_dispatch(Rules, _{
1478
1604
  rule_safety: RuleSafety,
1479
1605
  rule_verifiability: RuleVerifiability,
1480
1606
  semantic_completeness: SemanticCompleteness,
1481
- req_status_vocabulary: ReqStatusVocabulary
1607
+ req_status_vocabulary: ReqStatusVocabulary,
1608
+ proof_contract_symbols: ProofContractSymbols
1482
1609
  }) :-
1483
1610
  selected_rule(Rules, 'must-priority-coverage', check_must_priority_coverage, MustPriority),
1484
1611
  selected_rule(Rules, 'symbol-coverage', check_symbol_coverage, SymbolCoverage),
@@ -1496,7 +1623,8 @@ check_selected_dispatch(Rules, _{
1496
1623
  selected_rule(Rules, 'rule-safety', check_rule_safety, RuleSafety),
1497
1624
  selected_rule(Rules, 'rule-verifiability', check_rule_verifiability, RuleVerifiability),
1498
1625
  selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness),
1499
- selected_rule(Rules, 'req-status-vocabulary', check_req_status_vocabulary, ReqStatusVocabulary).
1626
+ selected_rule(Rules, 'req-status-vocabulary', check_req_status_vocabulary, ReqStatusVocabulary),
1627
+ selected_rule(Rules, 'proof-contract-symbols', check_proof_contract_symbols, ProofContractSymbols).
1500
1628
 
1501
1629
  selected_rule(Rules, Name, Goal, Violations) :-
1502
1630
  ( memberchk(Name, Rules)
@@ -1552,6 +1680,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
1552
1680
  check_rule_verifiability(RuleVerifiability),
1553
1681
  check_semantic_completeness(SemanticCompleteness),
1554
1682
  check_req_status_vocabulary(ReqStatusVocabulary),
1683
+ check_proof_contract_symbols(ProofContractSymbols),
1555
1684
  ViolationsDict = _{
1556
1685
  must_priority_coverage: MustPriority,
1557
1686
  symbol_coverage: SymbolCoverage,
@@ -1569,7 +1698,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
1569
1698
  rule_safety: RuleSafety,
1570
1699
  rule_verifiability: RuleVerifiability,
1571
1700
  semantic_completeness: SemanticCompleteness,
1572
- req_status_vocabulary: ReqStatusVocabulary
1701
+ req_status_vocabulary: ReqStatusVocabulary,
1702
+ proof_contract_symbols: ProofContractSymbols
1573
1703
  }.
1574
1704
 
1575
1705
  %% violations_dict_to_json(+ViolationsDict, -JsonDict)
package/src/discovery.pl CHANGED
@@ -8,6 +8,9 @@
8
8
  coverage_report_json/11,
9
9
  coverage_report_json/12,
10
10
  coverage_evidence_json/5,
11
+ requirement_proof_json/5,
12
+ requirement_proof_json/7,
13
+ symbol_proof_json/7,
11
14
  graph_expand_json/8
12
15
  ]).
13
16
 
@@ -40,6 +43,58 @@ coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset,
40
43
  coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
41
44
  coverage_report_json(By, Tags, IncludePassing, strict_snapshot, _{}, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString).
42
45
 
46
+ requirement_proof_json(ReqId, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
47
+ requirement_proof_json(ReqId, strict_snapshot, _{}, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString).
48
+
49
+ requirement_proof_json(ReqId, BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
50
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
51
+ kb_entity(ReqId, req, Props),
52
+ requirement_proof(ReqId, Props, Context, Proof),
53
+ put_dict(id, Proof, ReqId, ProofWithId),
54
+ dict_json_string(ProofWithId, JsonString).
55
+
56
+ symbol_proof_json(SymbolId, BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
57
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
58
+ kb_entity(SymbolId, symbol, _),
59
+ findall(ReqId,
60
+ (kb_relationship(implements, SymbolId, ReqId), kb_entity(ReqId, req, _)),
61
+ ReqIds0),
62
+ sort(ReqIds0, ReqIds),
63
+ maplist(requirement_proof_with_id(Context), ReqIds, Proofs),
64
+ symbol_explain_role(SymbolId, Role),
65
+ (kb:executable_test_symbol(SymbolId) -> ExecutableTest = true ; ExecutableTest = false),
66
+ findall(TestId, kb_relationship(executable_for, SymbolId, TestId), ExecutableFor0),
67
+ sort(ExecutableFor0, ExecutableFor),
68
+ findall(TestId, kb_relationship(covered_by, SymbolId, TestId), CoveredBy0),
69
+ sort(CoveredBy0, CoveredBy),
70
+ Response = _{
71
+ id: SymbolId,
72
+ symbolId: SymbolId,
73
+ role: Role,
74
+ executableTest: ExecutableTest,
75
+ implementingRequirements: ReqIds,
76
+ executableFor: ExecutableFor,
77
+ coveredBy: CoveredBy,
78
+ proofs: Proofs
79
+ },
80
+ dict_json_string(Response, JsonString).
81
+
82
+ requirement_proof_with_id(Context, ReqId, ProofWithId) :-
83
+ kb_entity(ReqId, req, Props),
84
+ requirement_proof(ReqId, Props, Context, Proof),
85
+ put_dict(id, Proof, ReqId, ProofWithId).
86
+
87
+ symbol_explain_role(SymbolId, executable_test) :-
88
+ kb:executable_test_symbol(SymbolId),
89
+ !.
90
+ symbol_explain_role(SymbolId, Role) :-
91
+ kb_entity(SymbolId, symbol, Props),
92
+ memberchk(symbol_role=RawRole, Props),
93
+ kb:normalize_term_atom(RawRole, Role),
94
+ Role \= '',
95
+ !.
96
+ symbol_explain_role(_SymbolId, unknown).
97
+
43
98
  % Slim per-requirement proof evidence for quality diagnostics (W1 push-down).
44
99
  % Projects only the fields the quality diagnostics consume - proof status,
45
100
  % the passing-e2e stage summary, and receipt-related gap codes - instead of
@@ -77,10 +77,10 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
77
77
  logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
78
78
  contradiction_stage(ReqId, LogicStage.status, Context, ContradictionStage),
79
79
  scenario_stage(ReqId, ScenarioStage, ScenarioIds),
80
- scenario_test_stage(ScenarioIds, ScenarioTestStage, _ScenarioTests),
80
+ scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
81
81
  passing_e2e_stage(ScenarioTestStage, Context, PassingE2eStage, PassingE2eTests),
82
82
  executable_symbol_stage(PassingE2eTests, ExecutableStage, ExecutableSymbols),
83
- production_symbol_stage(ReqId, PassingE2eTests, ProductionStage, ProductionSymbols),
83
+ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, PassingE2eStage.receiptEvidence, Context, ProductionStage, ProductionSymbols),
84
84
  source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, CoordinateStage),
85
85
  Stages = _{
86
86
  semanticInventory: SemanticStage,
@@ -103,7 +103,8 @@ requirement_proof(ReqId, ReqProps, Context, Proof) :-
103
103
  proofGaps: Gaps,
104
104
  proofAdvisories: Advisories,
105
105
  proofRepairs: Repairs,
106
- proofStages: Stages
106
+ proofStages: Stages,
107
+ testResolutions: ScenarioTestStage.resolutions
107
108
  }.
108
109
 
109
110
  exempt_requirement_proof(Reason, Proof) :-
@@ -532,6 +533,7 @@ scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
532
533
  ; Status = passed
533
534
  ),
534
535
  maplist(entity_source_ref, ScenarioTests, Sources),
536
+ maplist(test_resolution_record, ScenarioTests, Resolutions),
535
537
  Stage = _{
536
538
  status: Status,
537
539
  scenarios: ScenarioIds,
@@ -539,7 +541,8 @@ scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
539
541
  scenarioTestTargets: ScenarioTestTargets,
540
542
  invalidScenarioTestTargets: InvalidScenarioTestTargets,
541
543
  sources: Sources,
542
- obligations: Obligations
544
+ obligations: Obligations,
545
+ resolutions: Resolutions
543
546
  }.
544
547
 
545
548
  scenario_test_obligation(ScenarioId, Obligation) :-
@@ -1069,6 +1072,14 @@ test_missing_executable_symbol(TestId) :-
1069
1072
  \+ (kb_relationship(executable_for, SymbolId, TestId), kb_entity(SymbolId, symbol, _)).
1070
1073
 
1071
1074
  production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
1075
+ findall(TestId,
1076
+ (kb_relationship(specified_by, ReqId, ScenarioId),
1077
+ scenario_test(ScenarioId, TestId)),
1078
+ ScenarioTests0),
1079
+ sort(ScenarioTests0, ScenarioTests),
1080
+ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, [], _{}, Stage, ProductionSymbols).
1081
+
1082
+ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Stage, ProductionSymbols) :-
1072
1083
  findall(SymbolId,
1073
1084
  (kb_relationship(implements, SymbolId, ReqId),
1074
1085
  kb_entity(SymbolId, symbol, _),
@@ -1080,12 +1091,16 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
1080
1091
  include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredSymbols),
1081
1092
  production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, UncoveredSymbols, Status, StatusReason),
1082
1093
  maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
1094
+ production_coverage_explanations(
1095
+ ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
1096
+ PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Explanations),
1083
1097
  StageBase = _{
1084
1098
  status: Status,
1085
1099
  symbols: ProductionSymbols,
1086
1100
  structuralSymbols: StructuralSymbols,
1087
1101
  uncoveredSymbols: UncoveredSymbols,
1088
- coordinates: Coordinates
1102
+ coordinates: Coordinates,
1103
+ explanations: Explanations
1089
1104
  },
1090
1105
  ( Status == passed
1091
1106
  -> Stage = StageBase
@@ -1127,6 +1142,271 @@ production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests, U
1127
1142
  symbol_not_covered_by_tests(Tests, SymbolId) :-
1128
1143
  \+ (member(TestId, Tests), kb_relationship(covered_by, SymbolId, TestId)).
1129
1144
 
1145
+ production_coverage_explanations(ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
1146
+ PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Explanations) :-
1147
+ maplist(production_symbol_explanation(ReqId, UncoveredSymbols, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context),
1148
+ ProductionSymbols, ProductionExplanations),
1149
+ maplist(structural_symbol_explanation(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context),
1150
+ StructuralSymbols, StructuralExplanations),
1151
+ append(ProductionExplanations, StructuralExplanations, Explanations).
1152
+
1153
+ structural_symbol_explanation(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Explanation) :-
1154
+ symbol_classification(SymbolId, Classification),
1155
+ symbol_coverage_candidates(structural, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Candidates),
1156
+ explanation_reason_text(structural_unit_contract, ReasonText),
1157
+ Explanation = _{
1158
+ symbolId: SymbolId,
1159
+ requirementId: ReqId,
1160
+ classification: Classification,
1161
+ status: covered,
1162
+ reason: structural_unit_contract,
1163
+ reasonText: ReasonText,
1164
+ coverageCandidates: Candidates
1165
+ }.
1166
+
1167
+ production_symbol_explanation(ReqId, UncoveredSymbols, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Explanation) :-
1168
+ symbol_classification(SymbolId, Classification),
1169
+ symbol_coverage_candidates(behavioral, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Candidates),
1170
+ production_symbol_rollup(UncoveredSymbols, PassingE2eTests, SymbolId, Candidates, Status, Reason),
1171
+ explanation_reason_text(Reason, ReasonText),
1172
+ Explanation = _{
1173
+ symbolId: SymbolId,
1174
+ requirementId: ReqId,
1175
+ classification: Classification,
1176
+ status: Status,
1177
+ reason: Reason,
1178
+ reasonText: ReasonText,
1179
+ coverageCandidates: Candidates
1180
+ }.
1181
+
1182
+ production_symbol_rollup(_Uncovered, [], _SymbolId, _Candidates, uncovered, stage_blocked_no_passing_e2e) :-
1183
+ !.
1184
+ production_symbol_rollup(UncoveredSymbols, _Passing, SymbolId, Candidates, uncovered, Reason) :-
1185
+ memberchk(SymbolId, UncoveredSymbols),
1186
+ !,
1187
+ (Candidates == [] -> Reason = covered_by_missing ; Reason = no_qualifying_e2e_coverage).
1188
+ production_symbol_rollup(_Uncovered, _Passing, _SymbolId, _Candidates, covered, covered).
1189
+
1190
+ symbol_classification(SymbolId, Classification) :-
1191
+ kb_entity(SymbolId, symbol, Props),
1192
+ memberchk(symbol_role=RawRole, Props),
1193
+ normalize_atom(RawRole, Role),
1194
+ Role \= '',
1195
+ !,
1196
+ Classification = Role.
1197
+ symbol_classification(_SymbolId, unknown).
1198
+
1199
+ symbol_coverage_candidates(Mode, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, SymbolId, Candidates) :-
1200
+ findall(TestId, kb_relationship(covered_by, SymbolId, TestId), TestIds0),
1201
+ sort(TestIds0, TestIds),
1202
+ maplist(coverage_candidate_dict(Mode, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context), TestIds, Candidates).
1203
+
1204
+ coverage_candidate_dict(Mode, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, TestId, Candidate) :-
1205
+ coverage_candidate_qualification(Mode, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, TestId, Qualifies, Primary, Secondaries),
1206
+ explanation_reason_text(Primary, ReasonText),
1207
+ cheap_candidate_scope(TestId, Scope),
1208
+ reused_receipt_state(ReceiptEvidence, TestId, ReceiptState),
1209
+ CandidateBase = _{
1210
+ testId: TestId,
1211
+ relationship: covered_by,
1212
+ qualifies: Qualifies,
1213
+ reason: Primary,
1214
+ reasonText: ReasonText,
1215
+ secondaryReasons: Secondaries,
1216
+ scope: Scope
1217
+ },
1218
+ ( ReceiptState == none
1219
+ -> Candidate = CandidateBase
1220
+ ; put_dict(receiptState, CandidateBase, ReceiptState, Candidate)
1221
+ ).
1222
+
1223
+ % Shared candidate evaluator. Mode selects the coverage contract:
1224
+ % behavioral — production symbols require passing scenario-backed E2E
1225
+ % structural — type-shape symbols may also qualify via a unit contract
1226
+ coverage_candidate_qualification(_Mode, PassingE2eTests, _ScenarioTests, _ReceiptEvidence, _Context, TestId, true, covered, []) :-
1227
+ memberchk(TestId, PassingE2eTests),
1228
+ !.
1229
+ coverage_candidate_qualification(_Mode, _Passing, _Scenario, _Evidence, _Context, TestId, false, missing_test_entity, []) :-
1230
+ \+ kb_entity(TestId, test, _),
1231
+ !.
1232
+ coverage_candidate_qualification(structural, _Passing, _Scenario, _Evidence, _Context, TestId, true, structural_unit_contract, []) :-
1233
+ structural_unit_contract_test(TestId),
1234
+ !.
1235
+ coverage_candidate_qualification(_Mode, _Passing, ScenarioTests, ReceiptEvidence, Context, TestId, false, Primary, Secondaries) :-
1236
+ cheap_scope_reject_reason(TestId, ScopeReason),
1237
+ ( \+ memberchk(TestId, ScenarioTests)
1238
+ -> Primary = test_not_in_requirement_scenario_chain,
1239
+ maybe_singleton(ScopeReason, ScopeSecondaries),
1240
+ reused_receipt_reject_reasons(ReceiptEvidence, TestId, ReceiptSecondaries),
1241
+ append(ScopeSecondaries, ReceiptSecondaries, Secondaries)
1242
+ ; ScopeReason \= none
1243
+ -> Primary = ScopeReason,
1244
+ reused_receipt_reject_reasons(ReceiptEvidence, TestId, Secondaries)
1245
+ ; receipt_reject_for_in_chain(ReceiptEvidence, Context, TestId, Primary, Secondaries)
1246
+ ).
1247
+
1248
+ structural_unit_contract_test(TestId) :-
1249
+ kb_entity(TestId, test, TestProps),
1250
+ test_scope(TestProps, unit),
1251
+ memberchk(status=RawStatus, TestProps),
1252
+ normalize_atom(RawStatus, Status),
1253
+ memberchk(Status, [active, passing]).
1254
+
1255
+ cheap_candidate_scope(TestId, Scope) :-
1256
+ kb_entity(TestId, test, Props),
1257
+ !,
1258
+ test_scope(Props, Scope).
1259
+ cheap_candidate_scope(_TestId, unknown).
1260
+
1261
+ cheap_scope_reject_reason(TestId, Reason) :-
1262
+ cheap_candidate_scope(TestId, Scope),
1263
+ scope_reject_reason(Scope, Reason).
1264
+
1265
+ scope_reject_reason(unit, test_scope_is_unit) :- !.
1266
+ scope_reject_reason(end_to_end, none) :- !.
1267
+ scope_reject_reason(_Scope, test_scope_is_non_e2e).
1268
+
1269
+ maybe_singleton(none, []) :- !.
1270
+ maybe_singleton(Reason, [Reason]).
1271
+
1272
+ reused_receipt_state(Evidence, TestId, State) :-
1273
+ member(Item, Evidence),
1274
+ Item.testId == TestId,
1275
+ !,
1276
+ State = Item.state.
1277
+ reused_receipt_state(_Evidence, _TestId, none).
1278
+
1279
+ reused_receipt_item(Evidence, TestId, Item) :-
1280
+ member(Item, Evidence),
1281
+ Item.testId == TestId,
1282
+ !.
1283
+
1284
+ reused_receipt_reject_reasons(Evidence, TestId, Reasons) :-
1285
+ reused_receipt_item(Evidence, TestId, Item),
1286
+ !,
1287
+ receipt_item_reject_reason(Item, Reason),
1288
+ maybe_singleton(Reason, Reasons).
1289
+ reused_receipt_reject_reasons(_Evidence, _TestId, []).
1290
+
1291
+ receipt_reject_for_in_chain(Evidence, _Context, TestId, Primary, []) :-
1292
+ reused_receipt_item(Evidence, TestId, Item),
1293
+ !,
1294
+ receipt_item_reject_reason(Item, Reason),
1295
+ (Reason == none -> Primary = no_qualifying_e2e_coverage ; Primary = Reason).
1296
+ receipt_reject_for_in_chain(_Evidence, Context, TestId, Primary, []) :-
1297
+ is_dict(Context),
1298
+ Context \= _{},
1299
+ test_receipt_evidence(Context, TestId, Evidence),
1300
+ !,
1301
+ receipt_item_reject_reason(Evidence, Reason),
1302
+ (Reason == none -> Primary = no_qualifying_e2e_coverage ; Primary = Reason).
1303
+ receipt_reject_for_in_chain(_Evidence, _Context, _TestId, no_qualifying_e2e_coverage, []).
1304
+
1305
+ receipt_item_reject_reason(Item, stale_proof_receipt) :-
1306
+ Item.state == stale,
1307
+ get_dict(codeSnapshot, Item, _),
1308
+ !.
1309
+ receipt_item_reject_reason(Item, receipt_snapshot_mismatch) :-
1310
+ Item.state == stale,
1311
+ !.
1312
+ receipt_item_reject_reason(Item, Reason) :-
1313
+ receipt_state_reject_reason(Item.state, Reason).
1314
+
1315
+ receipt_state_reject_reason(missing, missing_proof_receipt) :- !.
1316
+ receipt_state_reject_reason(snapshot_unavailable, proof_snapshot_unavailable) :- !.
1317
+ receipt_state_reject_reason(contract_mismatch, receipt_contract_mismatch) :- !.
1318
+ receipt_state_reject_reason(invalid, invalid_proof_receipt) :- !.
1319
+ receipt_state_reject_reason(failed, failed_proof_receipt) :- !.
1320
+ receipt_state_reject_reason(not_end_to_end, none) :- !.
1321
+ receipt_state_reject_reason(stale, receipt_snapshot_mismatch) :- !.
1322
+ receipt_state_reject_reason(passed, none) :- !.
1323
+ receipt_state_reject_reason(none, none) :- !.
1324
+ receipt_state_reject_reason(_, none).
1325
+
1326
+ explanation_reason_text(Reason, Text) :-
1327
+ explanation_reason_text_def(Reason, Text),
1328
+ !.
1329
+ explanation_reason_text(Reason, Reason) :-
1330
+ atom(Reason).
1331
+
1332
+ explanation_reason_text_def(covered, "Qualifying covered_by to a passing scenario-backed E2E test").
1333
+ explanation_reason_text_def(covered_by_missing, "No covered_by relationship to any test").
1334
+ explanation_reason_text_def(no_qualifying_e2e_coverage, "covered_by relationships exist but none qualify as passing scenario-backed E2E coverage").
1335
+ explanation_reason_text_def(structural_unit_contract, "type-shape symbol is satisfied by a unit structural contract").
1336
+ explanation_reason_text_def(executable_test, "Symbol is executable test code linked with executable_for").
1337
+ explanation_reason_text_def(stage_blocked_no_passing_e2e, "Production coverage cannot be evaluated because no passing E2E evidence is available for the current snapshot").
1338
+ explanation_reason_text_def(missing_test_entity, "covered_by target is not a test entity").
1339
+ explanation_reason_text_def(test_not_in_requirement_scenario_chain, "Test is not in this requirement's scenario chain").
1340
+ explanation_reason_text_def(test_scope_is_unit, "Test verification_scope is unit, which cannot cover behavioral production symbols").
1341
+ explanation_reason_text_def(test_scope_is_non_e2e, "Test verification_scope is not end_to_end").
1342
+ explanation_reason_text_def(proof_snapshot_unavailable, "Workspace snapshot is unavailable so receipt currency cannot be proven").
1343
+ explanation_reason_text_def(missing_proof_receipt, "Scenario-backed E2E test has no kibi.proof-receipt.v1").
1344
+ explanation_reason_text_def(receipt_snapshot_mismatch, "Receipts exist but none match the current snapshot or binding").
1345
+ explanation_reason_text_def(stale_proof_receipt, "Current-snapshot receipt is older than the allowed proof age").
1346
+ explanation_reason_text_def(receipt_contract_mismatch, "Receipt does not match the current proof contract hash or fingerprint").
1347
+ explanation_reason_text_def(invalid_proof_receipt, "Receipt evidence is malformed or otherwise uncheckable").
1348
+ explanation_reason_text_def(failed_proof_receipt, "Latest current receipt outcome is not passed").
1349
+
1350
+ test_resolution_record(TestId, _{
1351
+ testId: TestId,
1352
+ requiredProofs: RequiredProofs,
1353
+ executableFor: ExecutableFor
1354
+ }) :-
1355
+ test_required_proof_refs(TestId, RequiredProofs),
1356
+ test_executable_for_refs(TestId, ExecutableFor).
1357
+
1358
+ test_required_proof_refs(TestId, Refs) :-
1359
+ kb_entity(TestId, test, Props),
1360
+ memberchk(proof_contract=RawContract, Props),
1361
+ proof_contract_dict(RawContract, Contract),
1362
+ contract_required_symbol_ids(Contract, SymbolIds),
1363
+ !,
1364
+ maplist(symbol_resolution_ref, SymbolIds, Refs).
1365
+ test_required_proof_refs(_TestId, []).
1366
+
1367
+ contract_required_symbol_ids(Contract, SymbolIds) :-
1368
+ ( get_dict(required_proofs, Contract, Raw)
1369
+ ; get_dict('required_proofs', Contract, Raw)
1370
+ ),
1371
+ !,
1372
+ inventory_entries(Raw, Entries),
1373
+ findall(SymbolId,
1374
+ (member(Entry, Entries), required_proof_entry_symbol(Entry, SymbolId)),
1375
+ SymbolIds0),
1376
+ sort(SymbolIds0, SymbolIds).
1377
+ contract_required_symbol_ids(_, []).
1378
+
1379
+ required_proof_entry_symbol(Entry, SymbolId) :-
1380
+ ( inventory_entry_field(Entry, symbol_id, Raw)
1381
+ ; inventory_entry_field(Entry, symbolId, Raw)
1382
+ ),
1383
+ normalize_atom(Raw, SymbolId),
1384
+ SymbolId \= ''.
1385
+
1386
+ test_executable_for_refs(TestId, Refs) :-
1387
+ findall(SymbolId,
1388
+ (kb_relationship(executable_for, SymbolId, TestId), kb_entity(SymbolId, symbol, _)),
1389
+ SymbolIds0),
1390
+ sort(SymbolIds0, SymbolIds),
1391
+ maplist(symbol_resolution_ref, SymbolIds, Refs).
1392
+
1393
+ symbol_resolution_ref(SymbolId, Ref) :-
1394
+ kb_entity(SymbolId, symbol, _),
1395
+ !,
1396
+ symbol_coordinate_ref(SymbolId, Coord),
1397
+ symbol_classification(SymbolId, Role),
1398
+ put_dict(_{role: Role, exists: true}, Coord, Ref).
1399
+ symbol_resolution_ref(SymbolId, _{
1400
+ id: SymbolId,
1401
+ sourceFile: '',
1402
+ sourceLine: 0,
1403
+ sourceColumn: 0,
1404
+ sourceEndLine: 0,
1405
+ sourceEndColumn: 0,
1406
+ role: unresolved,
1407
+ exists: false
1408
+ }).
1409
+
1130
1410
  source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, Stage) :-
1131
1411
  append(ExecutableSymbols, ProductionSymbols, Symbols0),
1132
1412
  sort(Symbols0, Symbols),
@@ -33,6 +33,7 @@ known_rule('strict-req-fact-pairing').
33
33
  known_rule('predicate-verifiability').
34
34
  known_rule('strict-readiness').
35
35
  known_rule('semantic-completeness').
36
+ known_rule('proof-contract-symbols').
36
37
 
37
38
  rule_enforcement_class('must-priority-coverage', canonical).
38
39
  rule_enforcement_class('symbol-coverage', canonical).
@@ -53,6 +54,7 @@ rule_enforcement_class('strict-req-fact-pairing', advisory).
53
54
  rule_enforcement_class('predicate-verifiability', advisory).
54
55
  rule_enforcement_class('strict-readiness', migration).
55
56
  rule_enforcement_class('semantic-completeness', migration).
57
+ rule_enforcement_class('proof-contract-symbols', advisory).
56
58
 
57
59
  rule_implementation('must-priority-coverage', prolog).
58
60
  rule_implementation('symbol-coverage', prolog).
@@ -73,6 +75,7 @@ rule_implementation('strict-req-fact-pairing', prolog).
73
75
  rule_implementation('predicate-verifiability', prolog).
74
76
  rule_implementation('strict-readiness', prolog).
75
77
  rule_implementation('semantic-completeness', prolog).
78
+ rule_implementation('proof-contract-symbols', prolog).
76
79
 
77
80
  rule_predicate('must-priority-coverage', check_must_priority_coverage).
78
81
  rule_predicate('symbol-coverage', check_symbol_coverage).
@@ -91,6 +94,7 @@ rule_predicate('strict-req-fact-pairing', check_strict_req_fact_pairing).
91
94
  rule_predicate('predicate-verifiability', check_predicate_verifiability).
92
95
  rule_predicate('strict-readiness', check_strict_readiness).
93
96
  rule_predicate('semantic-completeness', check_semantic_completeness).
97
+ rule_predicate('proof-contract-symbols', check_proof_contract_symbols).
94
98
 
95
99
  rule_predicate_arity('must-priority-coverage', 1).
96
100
  rule_predicate_arity('symbol-coverage', 1).
@@ -109,6 +113,7 @@ rule_predicate_arity('strict-req-fact-pairing', 1).
109
113
  rule_predicate_arity('predicate-verifiability', 1).
110
114
  rule_predicate_arity('strict-readiness', 1).
111
115
  rule_predicate_arity('semantic-completeness', 1).
116
+ rule_predicate_arity('proof-contract-symbols', 1).
112
117
 
113
118
  rule_description('must-priority-coverage', 'Every must-priority requirement must have a scenario and a test').
114
119
  rule_description('symbol-coverage', 'Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path').
@@ -129,3 +134,4 @@ rule_description('strict-req-fact-pairing', 'Detect requirements with incomplete
129
134
  rule_description('predicate-verifiability', 'Detect requires_predicate links that do not target ground fact_kind: predicate facts').
130
135
  rule_description('strict-readiness', 'Report strict contradiction-readiness levels for requirements that are still prose-only or otherwise not contradiction-ready').
131
136
  rule_description('semantic-completeness', 'Every inventoried assertive proposition is modeled or explicitly classified').
137
+ rule_description('proof-contract-symbols', 'Detect unresolved required_proofs.symbol_id values, type-shape required proofs, and proof_bindings.source_file disagreement with the named symbol sourceFile').