kibi-core 0.11.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 +2 -5
- package/schema/entities.pl +10 -4
- package/schema/validation.pl +3 -2
- package/src/checks.pl +199 -5
- package/src/discovery.pl +161 -4
- package/src/kb.pl +68 -5
- package/src/requirement_proof.pl +695 -145
- package/src/rule_registry.pl +137 -0
- package/src/status.pl +38 -19
- package/src/symbol_classification.pl +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-core",
|
|
3
|
-
"version": "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/schema/entities.pl
CHANGED
|
@@ -37,6 +37,8 @@ entity_property(req, semantic_inventory_version, string).
|
|
|
37
37
|
entity_property(req, semantic_source_field, string).
|
|
38
38
|
entity_property(req, semantic_source_hash, string).
|
|
39
39
|
entity_property(req, semantic_inventory, list_or_json).
|
|
40
|
+
entity_property(req, proof_exempt, boolean).
|
|
41
|
+
entity_property(req, proof_exempt_reason, string).
|
|
40
42
|
|
|
41
43
|
% Typed fact fields - only valid for fact entities
|
|
42
44
|
entity_property(fact, fact_kind, atom).
|
|
@@ -88,8 +90,9 @@ entity_property(symbol, sourceEndColumn, integer).
|
|
|
88
90
|
% Typed test verification fields - only valid for test entities
|
|
89
91
|
entity_property(test, verification_scope, atom).
|
|
90
92
|
entity_property(test, verification_perspective, atom).
|
|
91
|
-
entity_property(test,
|
|
92
|
-
entity_property(test,
|
|
93
|
+
entity_property(test, proof_contract, list_or_json).
|
|
94
|
+
entity_property(test, proof_bindings, string).
|
|
95
|
+
entity_property(test, proof_receipts, string).
|
|
93
96
|
|
|
94
97
|
% Required properties for all entity types
|
|
95
98
|
required_property(Type, id) :- entity_type(Type).
|
|
@@ -113,10 +116,13 @@ optional_property(req, semantic_inventory_version).
|
|
|
113
116
|
optional_property(req, semantic_source_field).
|
|
114
117
|
optional_property(req, semantic_source_hash).
|
|
115
118
|
optional_property(req, semantic_inventory).
|
|
119
|
+
optional_property(req, proof_exempt).
|
|
120
|
+
optional_property(req, proof_exempt_reason).
|
|
116
121
|
optional_property(test, verification_scope).
|
|
117
122
|
optional_property(test, verification_perspective).
|
|
118
|
-
optional_property(test,
|
|
119
|
-
optional_property(test,
|
|
123
|
+
optional_property(test, proof_contract).
|
|
124
|
+
optional_property(test, proof_bindings).
|
|
125
|
+
optional_property(test, proof_receipts).
|
|
120
126
|
|
|
121
127
|
% Documentation helpers
|
|
122
128
|
% list all entity types
|
package/schema/validation.pl
CHANGED
|
@@ -119,8 +119,9 @@ is_fact_only_field(claim_span_end).
|
|
|
119
119
|
% is_test_only_field(+Key) - true if Key is a test-specific field
|
|
120
120
|
is_test_only_field(verification_scope).
|
|
121
121
|
is_test_only_field(verification_perspective).
|
|
122
|
-
is_test_only_field(
|
|
123
|
-
is_test_only_field(
|
|
122
|
+
is_test_only_field(proof_contract).
|
|
123
|
+
is_test_only_field(proof_bindings).
|
|
124
|
+
is_test_only_field(proof_receipts).
|
|
124
125
|
|
|
125
126
|
% validate_fact_shape(+Kind, +Props)
|
|
126
127
|
validate_fact_shape(subject, Props) :-
|
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
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
check_rule_safety/1,
|
|
26
27
|
check_rule_verifiability/1,
|
|
27
28
|
check_semantic_completeness/1,
|
|
29
|
+
check_req_status_vocabulary/1,
|
|
28
30
|
run_checks_json/0, % Entry point for JSON output
|
|
29
31
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
30
32
|
]).
|
|
@@ -35,6 +37,8 @@
|
|
|
35
37
|
:- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
|
|
36
38
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
37
39
|
:- 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]).
|
|
40
|
+
% GENERATED registry facts; single source is schema/rule-registry.json.
|
|
41
|
+
:- use_module('rule_registry.pl', [known_rule/1]).
|
|
38
42
|
|
|
39
43
|
% Required fields for all entities
|
|
40
44
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -66,6 +70,8 @@ check_all(ViolationsDict) :-
|
|
|
66
70
|
check_rule_safety(RuleSafety),
|
|
67
71
|
check_rule_verifiability(RuleVerifiability),
|
|
68
72
|
check_semantic_completeness(SemanticCompleteness),
|
|
73
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
74
|
+
check_proof_contract_symbols(ProofContractSymbols),
|
|
69
75
|
ViolationsDict = _{
|
|
70
76
|
must_priority_coverage: MustPriority,
|
|
71
77
|
symbol_coverage: SymbolCoverage,
|
|
@@ -82,7 +88,9 @@ check_all(ViolationsDict) :-
|
|
|
82
88
|
logic_coverage: LogicCoverage,
|
|
83
89
|
rule_safety: RuleSafety,
|
|
84
90
|
rule_verifiability: RuleVerifiability,
|
|
85
|
-
semantic_completeness: SemanticCompleteness
|
|
91
|
+
semantic_completeness: SemanticCompleteness,
|
|
92
|
+
req_status_vocabulary: ReqStatusVocabulary,
|
|
93
|
+
proof_contract_symbols: ProofContractSymbols
|
|
86
94
|
}.
|
|
87
95
|
|
|
88
96
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -143,6 +151,17 @@ check_symbol_traceability(RequireAdr, Violations) :-
|
|
|
143
151
|
),
|
|
144
152
|
sort(Violations0, Violations).
|
|
145
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
|
+
|
|
146
165
|
symbol_traceability_violation(RequireAdr, violation(
|
|
147
166
|
'symbol-traceability',
|
|
148
167
|
SymbolId,
|
|
@@ -181,6 +200,118 @@ symbol_traceability_violation(RequireAdr, violation(
|
|
|
181
200
|
),
|
|
182
201
|
violation_source(SymbolId, symbol, Source).
|
|
183
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
|
+
|
|
184
315
|
%% check_no_dangling_refs(-Violations)
|
|
185
316
|
% Finds all relationships referencing non-existent entities.
|
|
186
317
|
check_no_dangling_refs(Violations) :-
|
|
@@ -363,6 +494,39 @@ deprecated_adr_violation(violation(
|
|
|
363
494
|
; Source = ""
|
|
364
495
|
).
|
|
365
496
|
|
|
497
|
+
%% check_req_status_vocabulary(-Violations)
|
|
498
|
+
% Rejects requirement statuses outside the canonical+legacy vocabulary.
|
|
499
|
+
% Requirement documents carrying ADR vocabulary (e.g. `status: accepted`)
|
|
500
|
+
% compile and pass schema validation, then silently fall out of
|
|
501
|
+
% current_req/1 — the proof ladder reports them not_applicable with no
|
|
502
|
+
% signal. This rule surfaces the vocabulary mismatch at check time instead.
|
|
503
|
+
canonical_req_statuses([open, in_progress, closed]).
|
|
504
|
+
legacy_req_statuses([active, approved]).
|
|
505
|
+
|
|
506
|
+
check_req_status_vocabulary(Violations) :-
|
|
507
|
+
findall(
|
|
508
|
+
Violation,
|
|
509
|
+
req_status_vocabulary_violation(Violation),
|
|
510
|
+
Violations
|
|
511
|
+
).
|
|
512
|
+
|
|
513
|
+
req_status_vocabulary_violation(violation(
|
|
514
|
+
'req-status-vocabulary',
|
|
515
|
+
ReqId,
|
|
516
|
+
Description,
|
|
517
|
+
Suggestion,
|
|
518
|
+
Source
|
|
519
|
+
)) :-
|
|
520
|
+
kb_entity(ReqId, req, Props),
|
|
521
|
+
memberchk(status=RawStatus, Props),
|
|
522
|
+
normalize_term_atom(RawStatus, StatusAtom),
|
|
523
|
+
canonical_req_statuses(Canonical),
|
|
524
|
+
legacy_req_statuses(Legacy),
|
|
525
|
+
\+ (memberchk(StatusAtom, Canonical) ; memberchk(StatusAtom, Legacy)),
|
|
526
|
+
format(string(Description), "Requirement status '~w' is not a current requirement status; it is silently excluded from the proof ladder", [StatusAtom]),
|
|
527
|
+
format(string(Suggestion), "Set status to one of open, in_progress, closed (legacy: active, approved). To park a current requirement out of E2E-proof scope, use proof_exempt: true with proof_exempt_reason instead of an ADR status", []),
|
|
528
|
+
violation_source(ReqId, req, Source).
|
|
529
|
+
|
|
366
530
|
%% check_strict_fact_shape(-Violations)
|
|
367
531
|
% Finds all strict facts (with fact_kind) that have malformed shape.
|
|
368
532
|
% Only checks facts with fact_kind present; legacy facts without fact_kind are ignored.
|
|
@@ -1412,7 +1576,18 @@ check_selected_json(Rules, JsonString) :-
|
|
|
1412
1576
|
JsonString
|
|
1413
1577
|
).
|
|
1414
1578
|
|
|
1415
|
-
check_selected(Rules,
|
|
1579
|
+
%% check_selected(+Rules, -ViolationsDict)
|
|
1580
|
+
% Run only the named aggregated checks. This is intentionally separate from
|
|
1581
|
+
% check_all/1 so focused diagnostics cannot evaluate unrelated rule queries.
|
|
1582
|
+
% Unknown rule names fail loudly: a typo must never masquerade as a clean,
|
|
1583
|
+
% violation-free run. Rules implemented on the TypeScript side (recorded in
|
|
1584
|
+
% rule_registry.pl with implementation typescript) are accepted here and
|
|
1585
|
+
% simply produce no Prolog-side violations.
|
|
1586
|
+
check_selected(Rules, ViolationsDict) :-
|
|
1587
|
+
require_known_rules(Rules),
|
|
1588
|
+
check_selected_dispatch(Rules, ViolationsDict).
|
|
1589
|
+
|
|
1590
|
+
check_selected_dispatch(Rules, _{
|
|
1416
1591
|
must_priority_coverage: MustPriority,
|
|
1417
1592
|
symbol_coverage: SymbolCoverage,
|
|
1418
1593
|
symbol_traceability: SymbolTraceability,
|
|
@@ -1428,7 +1603,9 @@ check_selected(Rules, _{
|
|
|
1428
1603
|
logic_coverage: LogicCoverage,
|
|
1429
1604
|
rule_safety: RuleSafety,
|
|
1430
1605
|
rule_verifiability: RuleVerifiability,
|
|
1431
|
-
semantic_completeness: SemanticCompleteness
|
|
1606
|
+
semantic_completeness: SemanticCompleteness,
|
|
1607
|
+
req_status_vocabulary: ReqStatusVocabulary,
|
|
1608
|
+
proof_contract_symbols: ProofContractSymbols
|
|
1432
1609
|
}) :-
|
|
1433
1610
|
selected_rule(Rules, 'must-priority-coverage', check_must_priority_coverage, MustPriority),
|
|
1434
1611
|
selected_rule(Rules, 'symbol-coverage', check_symbol_coverage, SymbolCoverage),
|
|
@@ -1445,7 +1622,9 @@ check_selected(Rules, _{
|
|
|
1445
1622
|
selected_rule(Rules, 'logic-coverage', check_logic_coverage, LogicCoverage),
|
|
1446
1623
|
selected_rule(Rules, 'rule-safety', check_rule_safety, RuleSafety),
|
|
1447
1624
|
selected_rule(Rules, 'rule-verifiability', check_rule_verifiability, RuleVerifiability),
|
|
1448
|
-
selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness)
|
|
1625
|
+
selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness),
|
|
1626
|
+
selected_rule(Rules, 'req-status-vocabulary', check_req_status_vocabulary, ReqStatusVocabulary),
|
|
1627
|
+
selected_rule(Rules, 'proof-contract-symbols', check_proof_contract_symbols, ProofContractSymbols).
|
|
1449
1628
|
|
|
1450
1629
|
selected_rule(Rules, Name, Goal, Violations) :-
|
|
1451
1630
|
( memberchk(Name, Rules)
|
|
@@ -1459,6 +1638,17 @@ selected_rule_with_options(Rules, Name, Goal, Violations) :-
|
|
|
1459
1638
|
; Violations = []
|
|
1460
1639
|
).
|
|
1461
1640
|
|
|
1641
|
+
%% require_known_rules(+Rules)
|
|
1642
|
+
% Fail closed on names outside the generated registry so a typo or a
|
|
1643
|
+
% stale cross-version call surfaces as an error instead of an empty result.
|
|
1644
|
+
require_known_rules(Rules) :-
|
|
1645
|
+
exclude(rule_registry:known_rule, Rules, Unknown),
|
|
1646
|
+
( Unknown == []
|
|
1647
|
+
-> true
|
|
1648
|
+
; throw(error(domain_error(check_rule, Unknown),
|
|
1649
|
+
context(check_selected, 'Unknown check rules requested')))
|
|
1650
|
+
).
|
|
1651
|
+
|
|
1462
1652
|
%% check_all_json_with_options(-JsonString, +RequireAdr)
|
|
1463
1653
|
% Returns all violations as JSON string with options.
|
|
1464
1654
|
% RequireAdr: if true, symbol-traceability also requires ADR constraints.
|
|
@@ -1489,6 +1679,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1489
1679
|
check_rule_safety(RuleSafety),
|
|
1490
1680
|
check_rule_verifiability(RuleVerifiability),
|
|
1491
1681
|
check_semantic_completeness(SemanticCompleteness),
|
|
1682
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
1683
|
+
check_proof_contract_symbols(ProofContractSymbols),
|
|
1492
1684
|
ViolationsDict = _{
|
|
1493
1685
|
must_priority_coverage: MustPriority,
|
|
1494
1686
|
symbol_coverage: SymbolCoverage,
|
|
@@ -1505,7 +1697,9 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1505
1697
|
logic_coverage: LogicCoverage,
|
|
1506
1698
|
rule_safety: RuleSafety,
|
|
1507
1699
|
rule_verifiability: RuleVerifiability,
|
|
1508
|
-
semantic_completeness: SemanticCompleteness
|
|
1700
|
+
semantic_completeness: SemanticCompleteness,
|
|
1701
|
+
req_status_vocabulary: ReqStatusVocabulary,
|
|
1702
|
+
proof_contract_symbols: ProofContractSymbols
|
|
1509
1703
|
}.
|
|
1510
1704
|
|
|
1511
1705
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/discovery.pl
CHANGED
|
@@ -5,13 +5,22 @@
|
|
|
5
5
|
find_gaps_json/8,
|
|
6
6
|
coverage_report_json/7,
|
|
7
7
|
coverage_report_json/10,
|
|
8
|
+
coverage_report_json/11,
|
|
9
|
+
coverage_report_json/12,
|
|
10
|
+
coverage_evidence_json/5,
|
|
11
|
+
requirement_proof_json/5,
|
|
12
|
+
requirement_proof_json/7,
|
|
13
|
+
symbol_proof_json/7,
|
|
8
14
|
graph_expand_json/8
|
|
9
15
|
]).
|
|
10
16
|
|
|
11
17
|
:- use_module(library(http/json)).
|
|
12
18
|
:- use_module(library(aggregate)).
|
|
19
|
+
:- use_module(library(apply)).
|
|
20
|
+
:- use_module(library(lists)).
|
|
21
|
+
:- use_module(library(pairs)).
|
|
13
22
|
:- use_module('kb.pl').
|
|
14
|
-
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof/4]).
|
|
23
|
+
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4]).
|
|
15
24
|
:- use_module('status.pl', [status_meta_dict/1]).
|
|
16
25
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
17
26
|
|
|
@@ -32,13 +41,157 @@ coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset,
|
|
|
32
41
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, unknown, '1970-01-01T00:00:00Z', 604800, JsonString).
|
|
33
42
|
|
|
34
43
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
35
|
-
|
|
44
|
+
coverage_report_json(By, Tags, IncludePassing, strict_snapshot, _{}, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString).
|
|
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
|
+
|
|
98
|
+
% Slim per-requirement proof evidence for quality diagnostics (W1 push-down).
|
|
99
|
+
% Projects only the fields the quality diagnostics consume - proof status,
|
|
100
|
+
% the passing-e2e stage summary, and receipt-related gap codes - instead of
|
|
101
|
+
% the full coverage row set, keeping the response small on large KBs.
|
|
102
|
+
coverage_evidence_json(Tags, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
103
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
|
|
104
|
+
findall(Evidence,
|
|
105
|
+
( kb_entity(Id, req, Props),
|
|
106
|
+
matches_tags(Tags, Props),
|
|
107
|
+
requirement_proof(Id, Props, ProofContext, Proof),
|
|
108
|
+
coverage_evidence_row(Id, Proof, Evidence)
|
|
109
|
+
),
|
|
110
|
+
Evidences),
|
|
111
|
+
Response = _{rows: Evidences},
|
|
112
|
+
dict_json_string(Response, JsonString).
|
|
113
|
+
|
|
114
|
+
coverage_evidence_row(Id, Proof, Evidence) :-
|
|
115
|
+
( get_dict(proofStatus, Proof, ProofStatus)
|
|
116
|
+
-> true
|
|
117
|
+
; ProofStatus = unknown
|
|
118
|
+
),
|
|
119
|
+
( get_dict(proofStages, Proof, Stages),
|
|
120
|
+
get_dict(passingE2e, Stages, PassingE2e),
|
|
121
|
+
get_dict(status, PassingE2e, PassingE2eStatus)
|
|
122
|
+
-> ( get_dict(tests, PassingE2e, PassingE2eTests)
|
|
123
|
+
-> true
|
|
124
|
+
; PassingE2eTests = []
|
|
125
|
+
)
|
|
126
|
+
; PassingE2eStatus = unknown,
|
|
127
|
+
PassingE2eTests = []
|
|
128
|
+
),
|
|
129
|
+
( get_dict(proofGaps, Proof, Gaps)
|
|
130
|
+
-> include(coverage_evidence_receipt_gap, Gaps, ReceiptGapCodes)
|
|
131
|
+
; ReceiptGapCodes = []
|
|
132
|
+
),
|
|
133
|
+
Evidence = _{
|
|
134
|
+
id: Id,
|
|
135
|
+
proofStatus: ProofStatus,
|
|
136
|
+
passingE2eStatus: PassingE2eStatus,
|
|
137
|
+
passingE2eTests: PassingE2eTests,
|
|
138
|
+
receiptGapCodes: ReceiptGapCodes
|
|
139
|
+
}.
|
|
140
|
+
|
|
141
|
+
coverage_evidence_receipt_gap(Gap) :-
|
|
142
|
+
( atom(Gap)
|
|
143
|
+
-> Atom = Gap
|
|
144
|
+
; format(atom(Atom), "~w", [Gap])
|
|
145
|
+
),
|
|
146
|
+
( sub_atom(Atom, _, _, _, 'proof_receipt')
|
|
147
|
+
; sub_atom(Atom, _, _, _, 'proof_contract')
|
|
148
|
+
; sub_atom(Atom, _, _, _, 'proof_snapshot')
|
|
149
|
+
).
|
|
150
|
+
|
|
151
|
+
% Per-contract receipt binding (W2): BindingMode strict_snapshot | per_contract
|
|
152
|
+
% with TestBindings mapping TestId -> current binding hash.
|
|
153
|
+
coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
154
|
+
% Binding mode applies to requirement rows only; other By values keep
|
|
155
|
+
% their snapshot-era arity so type/symbol reports are unchanged.
|
|
156
|
+
( By == req
|
|
157
|
+
-> coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows0, Summary)
|
|
158
|
+
; coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary)
|
|
159
|
+
),
|
|
36
160
|
sort_dict_rows(Rows0, SortedRows),
|
|
37
161
|
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
38
162
|
status_meta_dict(Meta),
|
|
39
163
|
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
40
164
|
dict_json_string(Response, JsonString).
|
|
41
165
|
|
|
166
|
+
% implements REQ-kibi-coverage-status-filter
|
|
167
|
+
% Proof-status filtered report. The status filter selects requirement rows by
|
|
168
|
+
% proofStatus (proven, missing, unresolved, not_applicable) and replaces the
|
|
169
|
+
% default include-passing filtering, so callers can enumerate exactly one
|
|
170
|
+
% slice — e.g. every not_applicable row together with its typed applicability
|
|
171
|
+
% reason — without diffing full exports by hand. The summary is always
|
|
172
|
+
% computed over all rows so filtered responses keep whole-KB counts.
|
|
173
|
+
coverage_report_json(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
174
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows0, Summary),
|
|
175
|
+
sort_dict_rows(MatchedRows0, SortedRows),
|
|
176
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
177
|
+
status_meta_dict(Meta),
|
|
178
|
+
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
179
|
+
dict_json_string(Response, JsonString).
|
|
180
|
+
|
|
181
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, [], IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
182
|
+
!,
|
|
183
|
+
coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
184
|
+
status_filtered_coverage_rows(req, Tags, _IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows, Summary) :-
|
|
185
|
+
!,
|
|
186
|
+
coverage_rows(req, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary),
|
|
187
|
+
include(req_row_proof_status_in(StatusFilter), Rows0, MatchedRows).
|
|
188
|
+
status_filtered_coverage_rows(By, Tags, _IncludePassing, _StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
189
|
+
coverage_rows(By, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
190
|
+
|
|
191
|
+
req_row_proof_status_in(StatusFilter, Row) :-
|
|
192
|
+
Status = Row.proofStatus,
|
|
193
|
+
memberchk(Status, StatusFilter).
|
|
194
|
+
|
|
42
195
|
graph_expand_json(SeedIds, Relationships, Direction, Depth, EntityTypes, MaxNodes, MaxEdges, JsonString) :-
|
|
43
196
|
sort(SeedIds, SeedSet),
|
|
44
197
|
bfs_layers(SeedSet, SeedSet, [], Relationships, Direction, Depth, MaxNodes, MaxEdges, SeenNodes0, SeenEdges0, Truncated0),
|
|
@@ -100,9 +253,9 @@ relationship_count(Id, Relationship, Count) :-
|
|
|
100
253
|
(kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
|
|
101
254
|
Count).
|
|
102
255
|
|
|
103
|
-
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
256
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows, Summary) :-
|
|
104
257
|
!,
|
|
105
|
-
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
|
|
258
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, ProofContext),
|
|
106
259
|
findall(Row,
|
|
107
260
|
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row),
|
|
108
261
|
AllRows),
|
|
@@ -140,6 +293,10 @@ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, _VerificationSna
|
|
|
140
293
|
length(Rows, Total),
|
|
141
294
|
Summary = _{total: Total}.
|
|
142
295
|
|
|
296
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
297
|
+
!,
|
|
298
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, strict_snapshot, _{}, Rows, Summary).
|
|
299
|
+
|
|
143
300
|
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row) :-
|
|
144
301
|
kb_entity(Id, req, Props),
|
|
145
302
|
matches_tags(Tags, Props),
|
package/src/kb.pl
CHANGED
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
:- use_module(library(thread)).
|
|
72
72
|
:- use_module(library(filesex)).
|
|
73
73
|
:- use_module(library(readutil)).
|
|
74
|
+
:- use_module(library(http/json)).
|
|
74
75
|
:- use_module(library(aggregate), [aggregate_all/3]).
|
|
75
76
|
:- use_module(library(lists), [sum_list/2]).
|
|
76
77
|
:- use_module(library(ordsets)).
|
|
@@ -156,8 +157,14 @@ kb_attach_journaled(Directory) :-
|
|
|
156
157
|
; make_directory_path(PersistencyDirectory)
|
|
157
158
|
),
|
|
158
159
|
% rdf_persistency owns the database lock for the lifetime of the engine.
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
% A failed attach throws a structured store_locked term carrying the
|
|
161
|
+
% ownership journal (if any) so callers can surface the holder or take
|
|
162
|
+
% over when the holder is provably dead.
|
|
163
|
+
catch(rdf_attach_db(PersistencyDirectory,
|
|
164
|
+
[access(read_write), silent(true), concurrency(4)]),
|
|
165
|
+
error(permission_error(_, _, _), Original),
|
|
166
|
+
kb_throw_store_locked(PersistencyDirectory, Directory, Original)),
|
|
167
|
+
catch(kb_write_lock_owner(Directory), _, true),
|
|
161
168
|
% Create RDF graph name from directory. Do not unload a graph here:
|
|
162
169
|
% rdf_attach_db has already restored it from its snapshot/journal. A
|
|
163
170
|
% staged migration can have been atomically moved after the graph was
|
|
@@ -229,6 +236,58 @@ kb_attach_legacy(Directory) :-
|
|
|
229
236
|
kb_rebuild_indexes.
|
|
230
237
|
|
|
231
238
|
|
|
239
|
+
%% Store lock ownership journal.
|
|
240
|
+
% rdf_persistency holds the branch-store lock while an engine is attached, but
|
|
241
|
+
% records nothing about the holder. The journal bridges that gap: kibi writes
|
|
242
|
+
% who attached (pid, workspace, boot id, started at) next to the lock so a
|
|
243
|
+
% later store_locked failure can surface the holder and self-heal when the
|
|
244
|
+
% holder is provably dead (crash, kill, removed worktree).
|
|
245
|
+
% implements REQ-core-journaled-engine-persistence
|
|
246
|
+
|
|
247
|
+
kb_lock_owner_path(Directory, Path) :-
|
|
248
|
+
atom_concat(Directory, '/.kibi-lock-owner.json', Path).
|
|
249
|
+
|
|
250
|
+
kb_write_lock_owner(Directory) :-
|
|
251
|
+
kb_lock_owner_path(Directory, Path),
|
|
252
|
+
current_prolog_flag(pid, Pid),
|
|
253
|
+
( exists_file('/proc/sys/kernel/random/boot_id')
|
|
254
|
+
-> catch(( read_file_to_string('/proc/sys/kernel/random/boot_id',
|
|
255
|
+
BootIdRaw, []),
|
|
256
|
+
split_string(BootIdRaw, "\r\n", "\r\n", [BootId|_])),
|
|
257
|
+
_,
|
|
258
|
+
BootId = "")
|
|
259
|
+
; BootId = ""
|
|
260
|
+
),
|
|
261
|
+
get_time(Now),
|
|
262
|
+
format_time(atom(StartedAt), '%FT%TZ', Now),
|
|
263
|
+
atom_concat(Path, '.tmp', TmpPath),
|
|
264
|
+
setup_call_cleanup(
|
|
265
|
+
open(TmpPath, write, Stream),
|
|
266
|
+
json_write_dict(Stream,
|
|
267
|
+
json([pid:Pid,
|
|
268
|
+
workspaceRoot:Directory,
|
|
269
|
+
bootId:BootId,
|
|
270
|
+
startedAt:StartedAt]),
|
|
271
|
+
[]),
|
|
272
|
+
(close(Stream), rename_file(TmpPath, Path))).
|
|
273
|
+
|
|
274
|
+
kb_remove_lock_owner :-
|
|
275
|
+
( kb_attached(Directory)
|
|
276
|
+
-> kb_lock_owner_path(Directory, Path),
|
|
277
|
+
catch(delete_file(Path), _, true)
|
|
278
|
+
; true
|
|
279
|
+
).
|
|
280
|
+
|
|
281
|
+
kb_throw_store_locked(PersistencyDirectory, Directory, Original) :-
|
|
282
|
+
kb_lock_owner_path(Directory, Path),
|
|
283
|
+
( exists_file(Path)
|
|
284
|
+
-> catch(read_file_to_string(Path, OwnerJson, []), _, OwnerJson = "")
|
|
285
|
+
; OwnerJson = ""
|
|
286
|
+
),
|
|
287
|
+
term_string(Original, OriginalText),
|
|
288
|
+
throw(error(permission_error(attach, kb_store, Directory),
|
|
289
|
+
kb_store_locked(OwnerJson, PersistencyDirectory, OriginalText))).
|
|
290
|
+
|
|
232
291
|
%% kb_detach
|
|
233
292
|
% Safely detach from KB without persisting pending changes.
|
|
234
293
|
% Call kb_save/0 explicitly before kb_detach/0 when durability is required.
|
|
@@ -238,7 +297,8 @@ kb_detach :-
|
|
|
238
297
|
-> (
|
|
239
298
|
( kb_storage_mode(journaled)
|
|
240
299
|
-> catch(rdf_flush_journals([min_size(16384)]), _, true),
|
|
241
|
-
catch(rdf_detach_db, _, true)
|
|
300
|
+
catch(rdf_detach_db, _, true),
|
|
301
|
+
catch(kb_remove_lock_owner, _, true)
|
|
242
302
|
; true
|
|
243
303
|
),
|
|
244
304
|
% Unload RDF graph from memory to prevent duplication on reattach
|
|
@@ -1836,10 +1896,13 @@ integer_property_key(Key) :-
|
|
|
1836
1896
|
literal_to_value(semantic_inventory, Literal, Value) :-
|
|
1837
1897
|
!,
|
|
1838
1898
|
structured_literal_value(Literal, Value).
|
|
1839
|
-
literal_to_value(
|
|
1899
|
+
literal_to_value(proof_receipts, Literal, Value) :-
|
|
1900
|
+
!,
|
|
1901
|
+
structured_literal_value(Literal, Value).
|
|
1902
|
+
literal_to_value(proof_contract, Literal, Value) :-
|
|
1840
1903
|
!,
|
|
1841
1904
|
structured_literal_value(Literal, Value).
|
|
1842
|
-
literal_to_value(
|
|
1905
|
+
literal_to_value(proof_bindings, Literal, Value) :-
|
|
1843
1906
|
!,
|
|
1844
1907
|
structured_literal_value(Literal, Value).
|
|
1845
1908
|
literal_to_value(_Key, Literal, Value) :-
|