kibi-core 0.11.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 +1 -1
- package/schema/entities.pl +10 -4
- package/schema/validation.pl +3 -2
- package/src/checks.pl +69 -5
- package/src/discovery.pl +106 -4
- package/src/kb.pl +68 -5
- package/src/requirement_proof.pl +413 -143
- package/src/rule_registry.pl +131 -0
- package/src/status.pl +38 -19
- package/src/symbol_classification.pl +47 -0
package/package.json
CHANGED
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
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
check_rule_safety/1,
|
|
26
26
|
check_rule_verifiability/1,
|
|
27
27
|
check_semantic_completeness/1,
|
|
28
|
+
check_req_status_vocabulary/1,
|
|
28
29
|
run_checks_json/0, % Entry point for JSON output
|
|
29
30
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
30
31
|
]).
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
:- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
|
|
36
37
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
37
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]).
|
|
38
41
|
|
|
39
42
|
% Required fields for all entities
|
|
40
43
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -66,6 +69,7 @@ check_all(ViolationsDict) :-
|
|
|
66
69
|
check_rule_safety(RuleSafety),
|
|
67
70
|
check_rule_verifiability(RuleVerifiability),
|
|
68
71
|
check_semantic_completeness(SemanticCompleteness),
|
|
72
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
69
73
|
ViolationsDict = _{
|
|
70
74
|
must_priority_coverage: MustPriority,
|
|
71
75
|
symbol_coverage: SymbolCoverage,
|
|
@@ -82,7 +86,8 @@ check_all(ViolationsDict) :-
|
|
|
82
86
|
logic_coverage: LogicCoverage,
|
|
83
87
|
rule_safety: RuleSafety,
|
|
84
88
|
rule_verifiability: RuleVerifiability,
|
|
85
|
-
semantic_completeness: SemanticCompleteness
|
|
89
|
+
semantic_completeness: SemanticCompleteness,
|
|
90
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
86
91
|
}.
|
|
87
92
|
|
|
88
93
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -363,6 +368,39 @@ deprecated_adr_violation(violation(
|
|
|
363
368
|
; Source = ""
|
|
364
369
|
).
|
|
365
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
|
+
|
|
366
404
|
%% check_strict_fact_shape(-Violations)
|
|
367
405
|
% Finds all strict facts (with fact_kind) that have malformed shape.
|
|
368
406
|
% Only checks facts with fact_kind present; legacy facts without fact_kind are ignored.
|
|
@@ -1412,7 +1450,18 @@ check_selected_json(Rules, JsonString) :-
|
|
|
1412
1450
|
JsonString
|
|
1413
1451
|
).
|
|
1414
1452
|
|
|
1415
|
-
check_selected(Rules,
|
|
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, _{
|
|
1416
1465
|
must_priority_coverage: MustPriority,
|
|
1417
1466
|
symbol_coverage: SymbolCoverage,
|
|
1418
1467
|
symbol_traceability: SymbolTraceability,
|
|
@@ -1428,7 +1477,8 @@ check_selected(Rules, _{
|
|
|
1428
1477
|
logic_coverage: LogicCoverage,
|
|
1429
1478
|
rule_safety: RuleSafety,
|
|
1430
1479
|
rule_verifiability: RuleVerifiability,
|
|
1431
|
-
semantic_completeness: SemanticCompleteness
|
|
1480
|
+
semantic_completeness: SemanticCompleteness,
|
|
1481
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
1432
1482
|
}) :-
|
|
1433
1483
|
selected_rule(Rules, 'must-priority-coverage', check_must_priority_coverage, MustPriority),
|
|
1434
1484
|
selected_rule(Rules, 'symbol-coverage', check_symbol_coverage, SymbolCoverage),
|
|
@@ -1445,7 +1495,8 @@ check_selected(Rules, _{
|
|
|
1445
1495
|
selected_rule(Rules, 'logic-coverage', check_logic_coverage, LogicCoverage),
|
|
1446
1496
|
selected_rule(Rules, 'rule-safety', check_rule_safety, RuleSafety),
|
|
1447
1497
|
selected_rule(Rules, 'rule-verifiability', check_rule_verifiability, RuleVerifiability),
|
|
1448
|
-
selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness)
|
|
1498
|
+
selected_rule(Rules, 'semantic-completeness', check_semantic_completeness, SemanticCompleteness),
|
|
1499
|
+
selected_rule(Rules, 'req-status-vocabulary', check_req_status_vocabulary, ReqStatusVocabulary).
|
|
1449
1500
|
|
|
1450
1501
|
selected_rule(Rules, Name, Goal, Violations) :-
|
|
1451
1502
|
( memberchk(Name, Rules)
|
|
@@ -1459,6 +1510,17 @@ selected_rule_with_options(Rules, Name, Goal, Violations) :-
|
|
|
1459
1510
|
; Violations = []
|
|
1460
1511
|
).
|
|
1461
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
|
+
|
|
1462
1524
|
%% check_all_json_with_options(-JsonString, +RequireAdr)
|
|
1463
1525
|
% Returns all violations as JSON string with options.
|
|
1464
1526
|
% RequireAdr: if true, symbol-traceability also requires ADR constraints.
|
|
@@ -1489,6 +1551,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1489
1551
|
check_rule_safety(RuleSafety),
|
|
1490
1552
|
check_rule_verifiability(RuleVerifiability),
|
|
1491
1553
|
check_semantic_completeness(SemanticCompleteness),
|
|
1554
|
+
check_req_status_vocabulary(ReqStatusVocabulary),
|
|
1492
1555
|
ViolationsDict = _{
|
|
1493
1556
|
must_priority_coverage: MustPriority,
|
|
1494
1557
|
symbol_coverage: SymbolCoverage,
|
|
@@ -1505,7 +1568,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1505
1568
|
logic_coverage: LogicCoverage,
|
|
1506
1569
|
rule_safety: RuleSafety,
|
|
1507
1570
|
rule_verifiability: RuleVerifiability,
|
|
1508
|
-
semantic_completeness: SemanticCompleteness
|
|
1571
|
+
semantic_completeness: SemanticCompleteness,
|
|
1572
|
+
req_status_vocabulary: ReqStatusVocabulary
|
|
1509
1573
|
}.
|
|
1510
1574
|
|
|
1511
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,6 +238,10 @@ 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),
|
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) :-
|