kibi-core 0.10.0 → 0.12.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 +5 -2
- package/schema/entities.pl +11 -2
- package/schema/relationships.pl +1 -0
- package/schema/validation.pl +3 -1
- package/src/checks.pl +189 -7
- package/src/discovery.pl +108 -5
- package/src/kb.pl +70 -4
- package/src/logic_ir.pl +1 -1
- package/src/requirement_proof.pl +630 -103
- package/src/rule_registry.pl +131 -0
- package/src/status.pl +144 -0
- 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.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Core Prolog modules and RDF graph logic for Kibi",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"node": ">=18"
|
|
9
9
|
},
|
|
10
10
|
"main": "./src/kb.pl",
|
|
11
|
-
"files": [
|
|
11
|
+
"files": [
|
|
12
|
+
"src/**/*.pl",
|
|
13
|
+
"schema/**/*.pl"
|
|
14
|
+
],
|
|
12
15
|
"license": "AGPL-3.0-or-later",
|
|
13
16
|
"author": "Piotr Franczyk",
|
|
14
17
|
"repository": {
|
package/schema/entities.pl
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:- module(kibi_entities, [entity_type/1, entity_property/3, required_property/2, optional_property/2]).
|
|
4
4
|
|
|
5
5
|
% Entity types
|
|
6
|
+
% implements REQ-004
|
|
6
7
|
entity_type(req).
|
|
7
8
|
entity_type(scenario).
|
|
8
9
|
entity_type(test).
|
|
@@ -36,6 +37,8 @@ entity_property(req, semantic_inventory_version, string).
|
|
|
36
37
|
entity_property(req, semantic_source_field, string).
|
|
37
38
|
entity_property(req, semantic_source_hash, string).
|
|
38
39
|
entity_property(req, semantic_inventory, list_or_json).
|
|
40
|
+
entity_property(req, proof_exempt, boolean).
|
|
41
|
+
entity_property(req, proof_exempt_reason, string).
|
|
39
42
|
|
|
40
43
|
% Typed fact fields - only valid for fact entities
|
|
41
44
|
entity_property(fact, fact_kind, atom).
|
|
@@ -87,7 +90,9 @@ entity_property(symbol, sourceEndColumn, integer).
|
|
|
87
90
|
% Typed test verification fields - only valid for test entities
|
|
88
91
|
entity_property(test, verification_scope, atom).
|
|
89
92
|
entity_property(test, verification_perspective, atom).
|
|
90
|
-
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).
|
|
91
96
|
|
|
92
97
|
% Required properties for all entity types
|
|
93
98
|
required_property(Type, id) :- entity_type(Type).
|
|
@@ -111,9 +116,13 @@ optional_property(req, semantic_inventory_version).
|
|
|
111
116
|
optional_property(req, semantic_source_field).
|
|
112
117
|
optional_property(req, semantic_source_hash).
|
|
113
118
|
optional_property(req, semantic_inventory).
|
|
119
|
+
optional_property(req, proof_exempt).
|
|
120
|
+
optional_property(req, proof_exempt_reason).
|
|
114
121
|
optional_property(test, verification_scope).
|
|
115
122
|
optional_property(test, verification_perspective).
|
|
116
|
-
optional_property(test,
|
|
123
|
+
optional_property(test, proof_contract).
|
|
124
|
+
optional_property(test, proof_bindings).
|
|
125
|
+
optional_property(test, proof_receipts).
|
|
117
126
|
|
|
118
127
|
% Documentation helpers
|
|
119
128
|
% list all entity types
|
package/schema/relationships.pl
CHANGED
package/schema/validation.pl
CHANGED
|
@@ -119,7 +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(
|
|
122
|
+
is_test_only_field(proof_contract).
|
|
123
|
+
is_test_only_field(proof_bindings).
|
|
124
|
+
is_test_only_field(proof_receipts).
|
|
123
125
|
|
|
124
126
|
% validate_fact_shape(+Kind, +Props)
|
|
125
127
|
validate_fact_shape(subject, Props) :-
|
package/src/checks.pl
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
:- module(checks, [
|
|
7
7
|
check_all/1, % Returns all violations as a dict
|
|
8
8
|
check_all_json/1, % Returns all violations as JSON string
|
|
9
|
+
check_selected_json/2, % Returns only selected rule violations as JSON
|
|
9
10
|
check_must_priority_coverage/1, % Returns list of must-priority violations
|
|
10
11
|
check_symbol_coverage/1, % Returns list of uncovered symbols
|
|
11
12
|
check_symbol_traceability/2, % Returns list of symbols lacking requirement traceability (ReqAdr option)
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
check_required_fields/1, % Returns list of missing required field violations
|
|
15
16
|
check_deprecated_adrs/1, % Returns list of deprecated ADR violations
|
|
16
17
|
check_domain_contradictions/1, % Returns list of contradiction violations
|
|
18
|
+
check_domain_contradictions_and_witnesses/2,
|
|
17
19
|
check_domain_contradiction_witnesses/1,
|
|
18
20
|
check_strict_fact_shape/1, % Returns list of malformed strict fact violations
|
|
19
21
|
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
check_rule_safety/1,
|
|
24
26
|
check_rule_verifiability/1,
|
|
25
27
|
check_semantic_completeness/1,
|
|
28
|
+
check_req_status_vocabulary/1,
|
|
26
29
|
run_checks_json/0, % Entry point for JSON output
|
|
27
30
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
28
31
|
]).
|
|
@@ -33,6 +36,8 @@
|
|
|
33
36
|
:- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
|
|
34
37
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
35
38
|
:- 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]).
|
|
39
|
+
% GENERATED registry facts; single source is schema/rule-registry.json.
|
|
40
|
+
:- use_module('rule_registry.pl', [known_rule/1]).
|
|
36
41
|
|
|
37
42
|
% Required fields for all entities
|
|
38
43
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -64,6 +69,7 @@ check_all(ViolationsDict) :-
|
|
|
64
69
|
check_rule_safety(RuleSafety),
|
|
65
70
|
check_rule_verifiability(RuleVerifiability),
|
|
66
71
|
check_semantic_completeness(SemanticCompleteness),
|
|
72
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
67
73
|
ViolationsDict = _{
|
|
68
74
|
must_priority_coverage: MustPriority,
|
|
69
75
|
symbol_coverage: SymbolCoverage,
|
|
@@ -80,7 +86,8 @@ check_all(ViolationsDict) :-
|
|
|
80
86
|
logic_coverage: LogicCoverage,
|
|
81
87
|
rule_safety: RuleSafety,
|
|
82
88
|
rule_verifiability: RuleVerifiability,
|
|
83
|
-
semantic_completeness: SemanticCompleteness
|
|
89
|
+
semantic_completeness: SemanticCompleteness,
|
|
90
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
84
91
|
}.
|
|
85
92
|
|
|
86
93
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -361,6 +368,39 @@ deprecated_adr_violation(violation(
|
|
|
361
368
|
; Source = ""
|
|
362
369
|
).
|
|
363
370
|
|
|
371
|
+
%% check_req_status_vocabulary(-Violations)
|
|
372
|
+
% Rejects requirement statuses outside the canonical+legacy vocabulary.
|
|
373
|
+
% Requirement documents carrying ADR vocabulary (e.g. `status: accepted`)
|
|
374
|
+
% compile and pass schema validation, then silently fall out of
|
|
375
|
+
% current_req/1 — the proof ladder reports them not_applicable with no
|
|
376
|
+
% signal. This rule surfaces the vocabulary mismatch at check time instead.
|
|
377
|
+
canonical_req_statuses([open, in_progress, closed]).
|
|
378
|
+
legacy_req_statuses([active, approved]).
|
|
379
|
+
|
|
380
|
+
check_req_status_vocabulary(Violations) :-
|
|
381
|
+
findall(
|
|
382
|
+
Violation,
|
|
383
|
+
req_status_vocabulary_violation(Violation),
|
|
384
|
+
Violations
|
|
385
|
+
).
|
|
386
|
+
|
|
387
|
+
req_status_vocabulary_violation(violation(
|
|
388
|
+
'req-status-vocabulary',
|
|
389
|
+
ReqId,
|
|
390
|
+
Description,
|
|
391
|
+
Suggestion,
|
|
392
|
+
Source
|
|
393
|
+
)) :-
|
|
394
|
+
kb_entity(ReqId, req, Props),
|
|
395
|
+
memberchk(status=RawStatus, Props),
|
|
396
|
+
normalize_term_atom(RawStatus, StatusAtom),
|
|
397
|
+
canonical_req_statuses(Canonical),
|
|
398
|
+
legacy_req_statuses(Legacy),
|
|
399
|
+
\+ (memberchk(StatusAtom, Canonical) ; memberchk(StatusAtom, Legacy)),
|
|
400
|
+
format(string(Description), "Requirement status '~w' is not a current requirement status; it is silently excluded from the proof ladder", [StatusAtom]),
|
|
401
|
+
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", []),
|
|
402
|
+
violation_source(ReqId, req, Source).
|
|
403
|
+
|
|
364
404
|
%% check_strict_fact_shape(-Violations)
|
|
365
405
|
% Finds all strict facts (with fact_kind) that have malformed shape.
|
|
366
406
|
% Only checks facts with fact_kind present; legacy facts without fact_kind are ignored.
|
|
@@ -591,6 +631,15 @@ check_domain_contradictions(Violations) :-
|
|
|
591
631
|
check_domain_contradiction_witnesses(Witnesses),
|
|
592
632
|
maplist(contradiction_witness_violation, Witnesses, Violations).
|
|
593
633
|
|
|
634
|
+
%% check_domain_contradictions_and_witnesses(-Violations, -Witnesses)
|
|
635
|
+
% Compute the contradiction witness graph once when a caller needs both the
|
|
636
|
+
% report violations and their exact evidence. The existing one-result
|
|
637
|
+
% predicates remain unchanged; this paired form avoids duplicate work in
|
|
638
|
+
% proof contexts without weakening any contradiction checks.
|
|
639
|
+
check_domain_contradictions_and_witnesses(Violations, Witnesses) :-
|
|
640
|
+
check_domain_contradiction_witnesses(Witnesses),
|
|
641
|
+
maplist(contradiction_witness_violation, Witnesses, Violations).
|
|
642
|
+
|
|
594
643
|
%% check_domain_contradiction_witnesses(-Witnesses)
|
|
595
644
|
% Exact, JSON-safe evidence for every strict-property, ground-predicate, and
|
|
596
645
|
% safe-rule contradiction result. Rule overlap that cannot be proven or
|
|
@@ -989,9 +1038,11 @@ predicate_verifiability_kind_label(Kind, Label) :-
|
|
|
989
1038
|
|
|
990
1039
|
%% check_logic_coverage(-Violations)
|
|
991
1040
|
% Validates explicitly declared atomic requirement claim manifests. Legacy
|
|
992
|
-
% requirements without logic_claims remain eligible for gradual backfill
|
|
993
|
-
%
|
|
994
|
-
%
|
|
1041
|
+
% requirements without logic_claims remain eligible for gradual backfill. A
|
|
1042
|
+
% modeled proposition must be grounded exactly once; ambiguous, ontology-gap,
|
|
1043
|
+
% and missing propositions remain explicit unresolved inventory states and are
|
|
1044
|
+
% not required to have a ground fact (missing is reported by semantic
|
|
1045
|
+
% completeness).
|
|
995
1046
|
check_logic_coverage(Violations) :-
|
|
996
1047
|
findall(Violation, logic_coverage_violation(Violation), Violations0),
|
|
997
1048
|
sort(Violations0, Violations).
|
|
@@ -1006,6 +1057,7 @@ logic_coverage_violation(violation(
|
|
|
1006
1057
|
kb:current_req(ReqId),
|
|
1007
1058
|
requirement_logic_claims(ReqId, ClaimKeys),
|
|
1008
1059
|
member(ClaimKey, ClaimKeys),
|
|
1060
|
+
claim_requires_grounding(ReqId, ClaimKey),
|
|
1009
1061
|
\+ grounded_requirement_claim(ReqId, ClaimKey),
|
|
1010
1062
|
format(
|
|
1011
1063
|
string(Description),
|
|
@@ -1014,6 +1066,24 @@ logic_coverage_violation(violation(
|
|
|
1014
1066
|
),
|
|
1015
1067
|
violation_source(ReqId, req, Source).
|
|
1016
1068
|
|
|
1069
|
+
logic_coverage_violation(violation(
|
|
1070
|
+
'logic-coverage',
|
|
1071
|
+
ReqId,
|
|
1072
|
+
Description,
|
|
1073
|
+
"Keep ontology gaps, ambiguities, and missing interpretations unresolved; only modeled propositions may have logical ground facts",
|
|
1074
|
+
Source
|
|
1075
|
+
)) :-
|
|
1076
|
+
kb:current_req(ReqId),
|
|
1077
|
+
requirement_inventory_status(ReqId, ClaimKey, Status),
|
|
1078
|
+
memberchk(Status, [ambiguous, ontology_gap, missing]),
|
|
1079
|
+
grounded_requirement_claim(ReqId, ClaimKey),
|
|
1080
|
+
format(
|
|
1081
|
+
string(Description),
|
|
1082
|
+
"Requirement grounds unresolved ~w proposition ~w",
|
|
1083
|
+
[Status, ClaimKey]
|
|
1084
|
+
),
|
|
1085
|
+
violation_source(ReqId, req, Source).
|
|
1086
|
+
|
|
1017
1087
|
logic_coverage_violation(violation(
|
|
1018
1088
|
'logic-coverage',
|
|
1019
1089
|
ReqId,
|
|
@@ -1076,6 +1146,28 @@ requirement_logic_claims(ReqId, ClaimKeys) :-
|
|
|
1076
1146
|
memberchk(logic_claims=RawClaimKeys, Props),
|
|
1077
1147
|
kb:normalize_term_atom_list(RawClaimKeys, ClaimKeys).
|
|
1078
1148
|
|
|
1149
|
+
claim_requires_grounding(ReqId, ClaimKey) :-
|
|
1150
|
+
requirement_inventory_status(ReqId, ClaimKey, modeled),
|
|
1151
|
+
!.
|
|
1152
|
+
claim_requires_grounding(ReqId, ClaimKey) :-
|
|
1153
|
+
\+ requirement_inventory_status(ReqId, ClaimKey, _).
|
|
1154
|
+
|
|
1155
|
+
requirement_inventory_status(ReqId, ClaimKey, Status) :-
|
|
1156
|
+
kb_entity(ReqId, req, Props),
|
|
1157
|
+
memberchk(semantic_inventory=RawInventory, Props),
|
|
1158
|
+
inventory_entries(RawInventory, Entries),
|
|
1159
|
+
member(Entry, Entries),
|
|
1160
|
+
checks_inventory_entry_field(Entry, claim_key, RawClaimKey),
|
|
1161
|
+
kb:normalize_term_atom(RawClaimKey, ClaimKey),
|
|
1162
|
+
inventory_entry_status(Entry, Status).
|
|
1163
|
+
|
|
1164
|
+
checks_inventory_entry_field(Entry, Key, Value) :-
|
|
1165
|
+
is_dict(Entry),
|
|
1166
|
+
get_dict(Key, Entry, Value).
|
|
1167
|
+
checks_inventory_entry_field(Entry, Key, Value) :-
|
|
1168
|
+
is_list(Entry),
|
|
1169
|
+
memberchk(Key=Value, Entry).
|
|
1170
|
+
|
|
1079
1171
|
grounded_requirement_claim(ReqId, ClaimKey) :-
|
|
1080
1172
|
grounded_requirement_claim_fact(ReqId, ClaimKey, _FactId).
|
|
1081
1173
|
|
|
@@ -1222,13 +1314,15 @@ rule_verifiability_description(Props, FactId, Description) :-
|
|
|
1222
1314
|
rule_verifiability_description(Props, FactId, Description) :-
|
|
1223
1315
|
memberchk(fact_kind=RawKind, Props),
|
|
1224
1316
|
normalize_term_atom(RawKind, rule),
|
|
1225
|
-
memberchk(rule_schema_id=
|
|
1317
|
+
memberchk(rule_schema_id=RawSchemaId, Props),
|
|
1318
|
+
normalize_term_atom(RawSchemaId, SchemaId),
|
|
1226
1319
|
\+ kb_entity(SchemaId, fact, _),
|
|
1227
1320
|
format(string(Description), "Rule fact ~w references missing rule schema ~w", [FactId, SchemaId]).
|
|
1228
1321
|
rule_verifiability_description(Props, FactId, Description) :-
|
|
1229
1322
|
memberchk(fact_kind=RawKind, Props),
|
|
1230
1323
|
normalize_term_atom(RawKind, rule),
|
|
1231
|
-
memberchk(rule_schema_id=
|
|
1324
|
+
memberchk(rule_schema_id=RawSchemaId, Props),
|
|
1325
|
+
normalize_term_atom(RawSchemaId, SchemaId),
|
|
1232
1326
|
kb_entity(SchemaId, fact, SchemaProps),
|
|
1233
1327
|
\+ valid_rule_schema_props(SchemaProps),
|
|
1234
1328
|
format(string(Description), "Rule fact ~w references ~w, which is not a rule_schema fact", [FactId, SchemaId]).
|
|
@@ -1286,6 +1380,10 @@ inventory_entries(Raw, Entries) :-
|
|
|
1286
1380
|
catch(atom_json_dict(Raw, JsonEntries, [value_string_as(string)]), _, fail),
|
|
1287
1381
|
is_list(JsonEntries)
|
|
1288
1382
|
-> Entries = JsonEntries
|
|
1383
|
+
; (atom(Raw) ; string(Raw)),
|
|
1384
|
+
catch(term_string(PrologEntries, Raw), _, fail),
|
|
1385
|
+
is_list(PrologEntries)
|
|
1386
|
+
-> Entries = PrologEntries
|
|
1289
1387
|
; Entries = []
|
|
1290
1388
|
).
|
|
1291
1389
|
|
|
@@ -1341,6 +1439,88 @@ check_all_json(JsonString) :-
|
|
|
1341
1439
|
JsonString
|
|
1342
1440
|
).
|
|
1343
1441
|
|
|
1442
|
+
%% check_selected_json(+Rules, -JsonString)
|
|
1443
|
+
% Run only the named aggregated checks. This is intentionally separate from
|
|
1444
|
+
% check_all/1 so focused diagnostics cannot evaluate unrelated rule queries.
|
|
1445
|
+
check_selected_json(Rules, JsonString) :-
|
|
1446
|
+
check_selected(Rules, ViolationsDict),
|
|
1447
|
+
violations_dict_to_json(ViolationsDict, JsonDict),
|
|
1448
|
+
with_output_to_string(
|
|
1449
|
+
json_write_dict(current_output, JsonDict, [width(0)]),
|
|
1450
|
+
JsonString
|
|
1451
|
+
).
|
|
1452
|
+
|
|
1453
|
+
%% check_selected(+Rules, -ViolationsDict)
|
|
1454
|
+
% Run only the named aggregated checks. This is intentionally separate from
|
|
1455
|
+
% check_all/1 so focused diagnostics cannot evaluate unrelated rule queries.
|
|
1456
|
+
% Unknown rule names fail loudly: a typo must never masquerade as a clean,
|
|
1457
|
+
% violation-free run. Rules implemented on the TypeScript side (recorded in
|
|
1458
|
+
% rule_registry.pl with implementation typescript) are accepted here and
|
|
1459
|
+
% simply produce no Prolog-side violations.
|
|
1460
|
+
check_selected(Rules, ViolationsDict) :-
|
|
1461
|
+
require_known_rules(Rules),
|
|
1462
|
+
check_selected_dispatch(Rules, ViolationsDict).
|
|
1463
|
+
|
|
1464
|
+
check_selected_dispatch(Rules, _{
|
|
1465
|
+
must_priority_coverage: MustPriority,
|
|
1466
|
+
symbol_coverage: SymbolCoverage,
|
|
1467
|
+
symbol_traceability: SymbolTraceability,
|
|
1468
|
+
no_dangling_refs: DanglingRefs,
|
|
1469
|
+
no_cycles: Cycles,
|
|
1470
|
+
required_fields: RequiredFields,
|
|
1471
|
+
deprecated_adr_no_successor: DeprecatedADRs,
|
|
1472
|
+
domain_contradictions: Contradictions,
|
|
1473
|
+
strict_fact_shape: StrictFactShape,
|
|
1474
|
+
strict_req_fact_pairing: StrictReqFactPairing,
|
|
1475
|
+
strict_readiness: StrictReadiness,
|
|
1476
|
+
predicate_verifiability: PredicateVerifiability,
|
|
1477
|
+
logic_coverage: LogicCoverage,
|
|
1478
|
+
rule_safety: RuleSafety,
|
|
1479
|
+
rule_verifiability: RuleVerifiability,
|
|
1480
|
+
semantic_completeness: SemanticCompleteness,
|
|
1481
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
1482
|
+
}) :-
|
|
1483
|
+
selected_rule(Rules, 'must-priority-coverage', check_must_priority_coverage, MustPriority),
|
|
1484
|
+
selected_rule(Rules, 'symbol-coverage', check_symbol_coverage, SymbolCoverage),
|
|
1485
|
+
selected_rule_with_options(Rules, 'symbol-traceability', check_symbol_traceability, SymbolTraceability),
|
|
1486
|
+
selected_rule(Rules, 'no-dangling-refs', check_no_dangling_refs, DanglingRefs),
|
|
1487
|
+
selected_rule(Rules, 'no-cycles', check_no_cycles, Cycles),
|
|
1488
|
+
selected_rule(Rules, 'required-fields', check_required_fields, RequiredFields),
|
|
1489
|
+
selected_rule(Rules, 'deprecated-adr-no-successor', check_deprecated_adrs, DeprecatedADRs),
|
|
1490
|
+
selected_rule(Rules, 'domain-contradictions', check_domain_contradictions, Contradictions),
|
|
1491
|
+
selected_rule(Rules, 'strict-fact-shape', check_strict_fact_shape, StrictFactShape),
|
|
1492
|
+
selected_rule(Rules, 'strict-req-fact-pairing', check_strict_req_fact_pairing, StrictReqFactPairing),
|
|
1493
|
+
selected_rule(Rules, 'strict-readiness', check_strict_readiness, StrictReadiness),
|
|
1494
|
+
selected_rule(Rules, 'predicate-verifiability', check_predicate_verifiability, PredicateVerifiability),
|
|
1495
|
+
selected_rule(Rules, 'logic-coverage', check_logic_coverage, LogicCoverage),
|
|
1496
|
+
selected_rule(Rules, 'rule-safety', check_rule_safety, RuleSafety),
|
|
1497
|
+
selected_rule(Rules, 'rule-verifiability', check_rule_verifiability, RuleVerifiability),
|
|
1498
|
+
selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness),
|
|
1499
|
+
selected_rule(Rules, 'req-status-vocabulary', check_req_status_vocabulary, ReqStatusVocabulary).
|
|
1500
|
+
|
|
1501
|
+
selected_rule(Rules, Name, Goal, Violations) :-
|
|
1502
|
+
( memberchk(Name, Rules)
|
|
1503
|
+
-> call(Goal, Violations)
|
|
1504
|
+
; Violations = []
|
|
1505
|
+
).
|
|
1506
|
+
|
|
1507
|
+
selected_rule_with_options(Rules, Name, Goal, Violations) :-
|
|
1508
|
+
( memberchk(Name, Rules)
|
|
1509
|
+
-> call(Goal, false, Violations)
|
|
1510
|
+
; Violations = []
|
|
1511
|
+
).
|
|
1512
|
+
|
|
1513
|
+
%% require_known_rules(+Rules)
|
|
1514
|
+
% Fail closed on names outside the generated registry so a typo or a
|
|
1515
|
+
% stale cross-version call surfaces as an error instead of an empty result.
|
|
1516
|
+
require_known_rules(Rules) :-
|
|
1517
|
+
exclude(rule_registry:known_rule, Rules, Unknown),
|
|
1518
|
+
( Unknown == []
|
|
1519
|
+
-> true
|
|
1520
|
+
; throw(error(domain_error(check_rule, Unknown),
|
|
1521
|
+
context(check_selected, 'Unknown check rules requested')))
|
|
1522
|
+
).
|
|
1523
|
+
|
|
1344
1524
|
%% check_all_json_with_options(-JsonString, +RequireAdr)
|
|
1345
1525
|
% Returns all violations as JSON string with options.
|
|
1346
1526
|
% RequireAdr: if true, symbol-traceability also requires ADR constraints.
|
|
@@ -1371,6 +1551,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1371
1551
|
check_rule_safety(RuleSafety),
|
|
1372
1552
|
check_rule_verifiability(RuleVerifiability),
|
|
1373
1553
|
check_semantic_completeness(SemanticCompleteness),
|
|
1554
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
1374
1555
|
ViolationsDict = _{
|
|
1375
1556
|
must_priority_coverage: MustPriority,
|
|
1376
1557
|
symbol_coverage: SymbolCoverage,
|
|
@@ -1387,7 +1568,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1387
1568
|
logic_coverage: LogicCoverage,
|
|
1388
1569
|
rule_safety: RuleSafety,
|
|
1389
1570
|
rule_verifiability: RuleVerifiability,
|
|
1390
|
-
semantic_completeness: SemanticCompleteness
|
|
1571
|
+
semantic_completeness: SemanticCompleteness,
|
|
1572
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
1391
1573
|
}.
|
|
1392
1574
|
|
|
1393
1575
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/discovery.pl
CHANGED
|
@@ -5,13 +5,19 @@
|
|
|
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,
|
|
8
11
|
graph_expand_json/8
|
|
9
12
|
]).
|
|
10
13
|
|
|
11
14
|
:- use_module(library(http/json)).
|
|
12
15
|
:- use_module(library(aggregate)).
|
|
16
|
+
:- use_module(library(apply)).
|
|
17
|
+
:- use_module(library(lists)).
|
|
18
|
+
:- use_module(library(pairs)).
|
|
13
19
|
:- use_module('kb.pl').
|
|
14
|
-
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof/4]).
|
|
20
|
+
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4]).
|
|
15
21
|
:- use_module('status.pl', [status_meta_dict/1]).
|
|
16
22
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
17
23
|
|
|
@@ -32,13 +38,105 @@ coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset,
|
|
|
32
38
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, unknown, '1970-01-01T00:00:00Z', 604800, JsonString).
|
|
33
39
|
|
|
34
40
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
35
|
-
|
|
41
|
+
coverage_report_json(By, Tags, IncludePassing, strict_snapshot, _{}, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString).
|
|
42
|
+
|
|
43
|
+
% Slim per-requirement proof evidence for quality diagnostics (W1 push-down).
|
|
44
|
+
% Projects only the fields the quality diagnostics consume - proof status,
|
|
45
|
+
% the passing-e2e stage summary, and receipt-related gap codes - instead of
|
|
46
|
+
% the full coverage row set, keeping the response small on large KBs.
|
|
47
|
+
coverage_evidence_json(Tags, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
48
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
|
|
49
|
+
findall(Evidence,
|
|
50
|
+
( kb_entity(Id, req, Props),
|
|
51
|
+
matches_tags(Tags, Props),
|
|
52
|
+
requirement_proof(Id, Props, ProofContext, Proof),
|
|
53
|
+
coverage_evidence_row(Id, Proof, Evidence)
|
|
54
|
+
),
|
|
55
|
+
Evidences),
|
|
56
|
+
Response = _{rows: Evidences},
|
|
57
|
+
dict_json_string(Response, JsonString).
|
|
58
|
+
|
|
59
|
+
coverage_evidence_row(Id, Proof, Evidence) :-
|
|
60
|
+
( get_dict(proofStatus, Proof, ProofStatus)
|
|
61
|
+
-> true
|
|
62
|
+
; ProofStatus = unknown
|
|
63
|
+
),
|
|
64
|
+
( get_dict(proofStages, Proof, Stages),
|
|
65
|
+
get_dict(passingE2e, Stages, PassingE2e),
|
|
66
|
+
get_dict(status, PassingE2e, PassingE2eStatus)
|
|
67
|
+
-> ( get_dict(tests, PassingE2e, PassingE2eTests)
|
|
68
|
+
-> true
|
|
69
|
+
; PassingE2eTests = []
|
|
70
|
+
)
|
|
71
|
+
; PassingE2eStatus = unknown,
|
|
72
|
+
PassingE2eTests = []
|
|
73
|
+
),
|
|
74
|
+
( get_dict(proofGaps, Proof, Gaps)
|
|
75
|
+
-> include(coverage_evidence_receipt_gap, Gaps, ReceiptGapCodes)
|
|
76
|
+
; ReceiptGapCodes = []
|
|
77
|
+
),
|
|
78
|
+
Evidence = _{
|
|
79
|
+
id: Id,
|
|
80
|
+
proofStatus: ProofStatus,
|
|
81
|
+
passingE2eStatus: PassingE2eStatus,
|
|
82
|
+
passingE2eTests: PassingE2eTests,
|
|
83
|
+
receiptGapCodes: ReceiptGapCodes
|
|
84
|
+
}.
|
|
85
|
+
|
|
86
|
+
coverage_evidence_receipt_gap(Gap) :-
|
|
87
|
+
( atom(Gap)
|
|
88
|
+
-> Atom = Gap
|
|
89
|
+
; format(atom(Atom), "~w", [Gap])
|
|
90
|
+
),
|
|
91
|
+
( sub_atom(Atom, _, _, _, 'proof_receipt')
|
|
92
|
+
; sub_atom(Atom, _, _, _, 'proof_contract')
|
|
93
|
+
; sub_atom(Atom, _, _, _, 'proof_snapshot')
|
|
94
|
+
).
|
|
95
|
+
|
|
96
|
+
% Per-contract receipt binding (W2): BindingMode strict_snapshot | per_contract
|
|
97
|
+
% with TestBindings mapping TestId -> current binding hash.
|
|
98
|
+
coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
99
|
+
% Binding mode applies to requirement rows only; other By values keep
|
|
100
|
+
% their snapshot-era arity so type/symbol reports are unchanged.
|
|
101
|
+
( By == req
|
|
102
|
+
-> coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows0, Summary)
|
|
103
|
+
; coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary)
|
|
104
|
+
),
|
|
36
105
|
sort_dict_rows(Rows0, SortedRows),
|
|
37
106
|
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
38
107
|
status_meta_dict(Meta),
|
|
39
108
|
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
40
109
|
dict_json_string(Response, JsonString).
|
|
41
110
|
|
|
111
|
+
% implements REQ-kibi-coverage-status-filter
|
|
112
|
+
% Proof-status filtered report. The status filter selects requirement rows by
|
|
113
|
+
% proofStatus (proven, missing, unresolved, not_applicable) and replaces the
|
|
114
|
+
% default include-passing filtering, so callers can enumerate exactly one
|
|
115
|
+
% slice — e.g. every not_applicable row together with its typed applicability
|
|
116
|
+
% reason — without diffing full exports by hand. The summary is always
|
|
117
|
+
% computed over all rows so filtered responses keep whole-KB counts.
|
|
118
|
+
coverage_report_json(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
119
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows0, Summary),
|
|
120
|
+
sort_dict_rows(MatchedRows0, SortedRows),
|
|
121
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
122
|
+
status_meta_dict(Meta),
|
|
123
|
+
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
124
|
+
dict_json_string(Response, JsonString).
|
|
125
|
+
|
|
126
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, [], IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
127
|
+
!,
|
|
128
|
+
coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
129
|
+
status_filtered_coverage_rows(req, Tags, _IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows, Summary) :-
|
|
130
|
+
!,
|
|
131
|
+
coverage_rows(req, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary),
|
|
132
|
+
include(req_row_proof_status_in(StatusFilter), Rows0, MatchedRows).
|
|
133
|
+
status_filtered_coverage_rows(By, Tags, _IncludePassing, _StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
134
|
+
coverage_rows(By, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
135
|
+
|
|
136
|
+
req_row_proof_status_in(StatusFilter, Row) :-
|
|
137
|
+
Status = Row.proofStatus,
|
|
138
|
+
memberchk(Status, StatusFilter).
|
|
139
|
+
|
|
42
140
|
graph_expand_json(SeedIds, Relationships, Direction, Depth, EntityTypes, MaxNodes, MaxEdges, JsonString) :-
|
|
43
141
|
sort(SeedIds, SeedSet),
|
|
44
142
|
bfs_layers(SeedSet, SeedSet, [], Relationships, Direction, Depth, MaxNodes, MaxEdges, SeenNodes0, SeenEdges0, Truncated0),
|
|
@@ -100,9 +198,9 @@ relationship_count(Id, Relationship, Count) :-
|
|
|
100
198
|
(kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
|
|
101
199
|
Count).
|
|
102
200
|
|
|
103
|
-
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
201
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows, Summary) :-
|
|
104
202
|
!,
|
|
105
|
-
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
|
|
203
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, ProofContext),
|
|
106
204
|
findall(Row,
|
|
107
205
|
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row),
|
|
108
206
|
AllRows),
|
|
@@ -140,10 +238,14 @@ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, _VerificationSna
|
|
|
140
238
|
length(Rows, Total),
|
|
141
239
|
Summary = _{total: Total}.
|
|
142
240
|
|
|
241
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
242
|
+
!,
|
|
243
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, strict_snapshot, _{}, Rows, Summary).
|
|
244
|
+
|
|
143
245
|
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row) :-
|
|
144
246
|
kb_entity(Id, req, Props),
|
|
145
247
|
matches_tags(Tags, Props),
|
|
146
|
-
entity_title_status_source(Props, Title, Status,
|
|
248
|
+
entity_title_status_source(Props, Title, Status, Source),
|
|
147
249
|
entity_priority(Props, Priority),
|
|
148
250
|
count_distinct_targets(specified_by, Id, source, ScenarioCount),
|
|
149
251
|
requirement_test_count(Id, TestCount),
|
|
@@ -156,6 +258,7 @@ requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row) :-
|
|
|
156
258
|
type: req,
|
|
157
259
|
title: Title,
|
|
158
260
|
status: Status,
|
|
261
|
+
source: Source,
|
|
159
262
|
priority: Priority,
|
|
160
263
|
scenarioCount: ScenarioCount,
|
|
161
264
|
testCount: TestCount,
|
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,7 +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) :-
|
|
1903
|
+
!,
|
|
1904
|
+
structured_literal_value(Literal, Value).
|
|
1905
|
+
literal_to_value(proof_bindings, Literal, Value) :-
|
|
1840
1906
|
!,
|
|
1841
1907
|
structured_literal_value(Literal, Value).
|
|
1842
1908
|
literal_to_value(_Key, Literal, Value) :-
|
package/src/logic_ir.pl
CHANGED
|
@@ -530,7 +530,7 @@ rule_dependency_edge(
|
|
|
530
530
|
logic_predicate_signature(atom(Namespace, Name, Args, _Polarity, _ClosedWorld), predicate(Namespace, Name, Arity)) :-
|
|
531
531
|
length(Args, Arity).
|
|
532
532
|
|
|
533
|
-
expression_dependency(
|
|
533
|
+
expression_dependency(Atom, Atom, positive) :-
|
|
534
534
|
Atom = atom(_, _, _, _, _).
|
|
535
535
|
expression_dependency(not(Atom), Atom, negative).
|
|
536
536
|
expression_dependency(all(Items), Atom, Sign) :- member(Item, Items), expression_dependency(Item, Atom, Sign).
|