kibi-core 0.7.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/schema/entities.pl +28 -0
- package/schema/relationships.pl +2 -0
- package/schema/validation.pl +41 -32
- package/src/checks.pl +410 -19
- package/src/discovery.pl +88 -18
- package/src/kb.pl +256 -19
- package/src/logic_ir.pl +629 -0
- package/src/requirement_proof.pl +837 -0
package/src/discovery.pl
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
:- module(discovery, [
|
|
5
5
|
find_gaps_json/8,
|
|
6
6
|
coverage_report_json/7,
|
|
7
|
+
coverage_report_json/10,
|
|
7
8
|
graph_expand_json/8
|
|
8
9
|
]).
|
|
9
10
|
|
|
10
11
|
:- use_module(library(http/json)).
|
|
11
12
|
:- use_module(library(aggregate)).
|
|
12
13
|
:- use_module('kb.pl').
|
|
14
|
+
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof/4]).
|
|
13
15
|
:- use_module('status.pl', [status_meta_dict/1]).
|
|
14
16
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
15
17
|
|
|
@@ -27,7 +29,10 @@ find_gaps_json(TypeFilter, MissingRelationships, PresentRelationships, Tags, Sou
|
|
|
27
29
|
dict_json_string(Response, JsonString).
|
|
28
30
|
|
|
29
31
|
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, JsonString) :-
|
|
30
|
-
|
|
32
|
+
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, unknown, '1970-01-01T00:00:00Z', 604800, JsonString).
|
|
33
|
+
|
|
34
|
+
coverage_report_json(By, Tags, IncludePassing, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
35
|
+
coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows0, Summary),
|
|
31
36
|
sort_dict_rows(Rows0, SortedRows),
|
|
32
37
|
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
33
38
|
status_meta_dict(Meta),
|
|
@@ -95,14 +100,15 @@ relationship_count(Id, Relationship, Count) :-
|
|
|
95
100
|
(kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
|
|
96
101
|
Count).
|
|
97
102
|
|
|
98
|
-
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, Rows, Summary) :-
|
|
103
|
+
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
99
104
|
!,
|
|
105
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
|
|
100
106
|
findall(Row,
|
|
101
|
-
requirement_coverage_row(Tags, IncludeTransitive, Row),
|
|
107
|
+
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row),
|
|
102
108
|
AllRows),
|
|
103
109
|
filter_req_coverage_rows(IncludePassing, AllRows, Rows),
|
|
104
110
|
coverage_summary(AllRows, Summary).
|
|
105
|
-
coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, Rows, Summary) :-
|
|
111
|
+
coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, _VerificationSnapshot, _CheckedAt, _MaxAgeSeconds, Rows, Summary) :-
|
|
106
112
|
!,
|
|
107
113
|
findall(Row,
|
|
108
114
|
symbol_coverage_row(Tags, Row),
|
|
@@ -110,10 +116,23 @@ coverage_rows(symbol, Tags, IncludePassing, _IncludeTransitive, Rows, Summary) :
|
|
|
110
116
|
filter_symbol_coverage_rows(IncludePassing, AllRows, Rows),
|
|
111
117
|
length(AllRows, Total),
|
|
112
118
|
include(symbol_row_fully_covered, AllRows, CoveredRows),
|
|
119
|
+
include(symbol_row_uncovered, AllRows, UncoveredRows),
|
|
120
|
+
include(symbol_row_not_applicable, AllRows, NotApplicableRows),
|
|
121
|
+
include(symbol_row_mixed_role, AllRows, MixedRoleRows),
|
|
113
122
|
length(CoveredRows, Covered),
|
|
114
|
-
Uncovered
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
length(UncoveredRows, Uncovered),
|
|
124
|
+
length(NotApplicableRows, NotApplicable),
|
|
125
|
+
length(MixedRoleRows, MixedRole),
|
|
126
|
+
Applicable is Total - NotApplicable,
|
|
127
|
+
Summary = _{
|
|
128
|
+
total: Total,
|
|
129
|
+
applicable: Applicable,
|
|
130
|
+
fullyCovered: Covered,
|
|
131
|
+
uncovered: Uncovered,
|
|
132
|
+
notApplicable: NotApplicable,
|
|
133
|
+
mixedRole: MixedRole
|
|
134
|
+
}.
|
|
135
|
+
coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, _VerificationSnapshot, _CheckedAt, _MaxAgeSeconds, Rows, Summary) :-
|
|
117
136
|
findall(Type-Count,
|
|
118
137
|
type_entity_count(Type, Count),
|
|
119
138
|
Pairs),
|
|
@@ -121,7 +140,7 @@ coverage_rows(type, _Tags, _IncludePassing, _IncludeTransitive, Rows, Summary) :
|
|
|
121
140
|
length(Rows, Total),
|
|
122
141
|
Summary = _{total: Total}.
|
|
123
142
|
|
|
124
|
-
requirement_coverage_row(Tags, IncludeTransitive, Row) :-
|
|
143
|
+
requirement_coverage_row(Tags, IncludeTransitive, ProofContext, Row) :-
|
|
125
144
|
kb_entity(Id, req, Props),
|
|
126
145
|
matches_tags(Tags, Props),
|
|
127
146
|
entity_title_status_source(Props, Title, Status, _Source),
|
|
@@ -132,7 +151,7 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
|
|
|
132
151
|
count_transitive_symbols(Id, IncludeTransitive, TransitiveSymbolCount),
|
|
133
152
|
requirement_coverage_state(Id, Props, Gaps, Evaluated, CoverageStatus),
|
|
134
153
|
requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, CoverageEvidence),
|
|
135
|
-
|
|
154
|
+
BaseRow = _{
|
|
136
155
|
id: Id,
|
|
137
156
|
type: req,
|
|
138
157
|
title: Title,
|
|
@@ -151,7 +170,9 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
|
|
|
151
170
|
scenarioTests: CoverageEvidence.scenarioTests,
|
|
152
171
|
testStatuses: CoverageEvidence.testStatuses,
|
|
153
172
|
verificationScopes: CoverageEvidence.verificationScopes
|
|
154
|
-
}
|
|
173
|
+
},
|
|
174
|
+
requirement_proof(Id, Props, ProofContext, Proof),
|
|
175
|
+
put_dict(Proof, BaseRow, Row).
|
|
155
176
|
|
|
156
177
|
symbol_coverage_row(Tags, Row) :-
|
|
157
178
|
kb_entity(Id, symbol, Props),
|
|
@@ -159,19 +180,32 @@ symbol_coverage_row(Tags, Row) :-
|
|
|
159
180
|
entity_title_status_source(Props, Title, Status, _Source),
|
|
160
181
|
count_direct_requirements(Id, DirectRequirementCount),
|
|
161
182
|
count_direct_tests(Id, TestCount),
|
|
162
|
-
(
|
|
163
|
-
|
|
183
|
+
count_executable_tests(Id, ExecutableTestCount),
|
|
184
|
+
( mixed_role_symbol(Id)
|
|
185
|
+
-> TraceabilityRole = mixed,
|
|
186
|
+
Gaps = [invalid_mixed_symbol_role],
|
|
164
187
|
CoverageStatus = uncovered
|
|
165
|
-
;
|
|
166
|
-
|
|
188
|
+
; executable_test_symbol(Id)
|
|
189
|
+
-> TraceabilityRole = executable_test,
|
|
190
|
+
Gaps = [],
|
|
191
|
+
CoverageStatus = not_applicable
|
|
192
|
+
; TraceabilityRole = production,
|
|
193
|
+
( symbol_no_req_coverage(Id, _)
|
|
194
|
+
-> Gaps = [missing_requirement],
|
|
195
|
+
CoverageStatus = uncovered
|
|
196
|
+
; Gaps = [],
|
|
197
|
+
CoverageStatus = fully_covered
|
|
198
|
+
)
|
|
167
199
|
),
|
|
168
200
|
Row = _{
|
|
169
201
|
id: Id,
|
|
170
202
|
type: symbol,
|
|
171
203
|
title: Title,
|
|
172
204
|
status: Status,
|
|
205
|
+
traceabilityRole: TraceabilityRole,
|
|
173
206
|
directRequirementCount: DirectRequirementCount,
|
|
174
207
|
testCount: TestCount,
|
|
208
|
+
executableTestCount: ExecutableTestCount,
|
|
175
209
|
gaps: Gaps,
|
|
176
210
|
coverageStatus: CoverageStatus
|
|
177
211
|
}.
|
|
@@ -185,6 +219,10 @@ coverage_summary(Rows, Summary) :-
|
|
|
185
219
|
include(req_row_missing_scenario, Rows, MissingScenarioRows),
|
|
186
220
|
include(req_row_missing_test, Rows, MissingTestRows),
|
|
187
221
|
include(req_row_missing_both, Rows, MissingBothRows),
|
|
222
|
+
include(req_row_proof_proven, Rows, ProofProvenRows),
|
|
223
|
+
include(req_row_proof_unresolved, Rows, ProofUnresolvedRows),
|
|
224
|
+
include(req_row_proof_missing, Rows, ProofMissingRows),
|
|
225
|
+
include(req_row_proof_not_applicable, Rows, ProofNotApplicableRows),
|
|
188
226
|
length(EvaluatedRows, Evaluated),
|
|
189
227
|
length(FullyCoveredRows, FullyCovered),
|
|
190
228
|
length(UncoveredRows, Uncovered),
|
|
@@ -192,6 +230,10 @@ coverage_summary(Rows, Summary) :-
|
|
|
192
230
|
length(MissingScenarioRows, MissingScenario),
|
|
193
231
|
length(MissingTestRows, MissingTest),
|
|
194
232
|
length(MissingBothRows, MissingScenarioAndTest),
|
|
233
|
+
length(ProofProvenRows, ProofProven),
|
|
234
|
+
length(ProofUnresolvedRows, ProofUnresolved),
|
|
235
|
+
length(ProofMissingRows, ProofMissing),
|
|
236
|
+
length(ProofNotApplicableRows, ProofNotApplicable),
|
|
195
237
|
Summary = _{
|
|
196
238
|
total: Total,
|
|
197
239
|
evaluated: Evaluated,
|
|
@@ -200,7 +242,11 @@ coverage_summary(Rows, Summary) :-
|
|
|
200
242
|
notApplicable: NotApplicable,
|
|
201
243
|
missingScenario: MissingScenario,
|
|
202
244
|
missingTest: MissingTest,
|
|
203
|
-
missingScenarioAndTest: MissingScenarioAndTest
|
|
245
|
+
missingScenarioAndTest: MissingScenarioAndTest,
|
|
246
|
+
proofProven: ProofProven,
|
|
247
|
+
proofUnresolved: ProofUnresolved,
|
|
248
|
+
proofMissing: ProofMissing,
|
|
249
|
+
proofNotApplicable: ProofNotApplicable
|
|
204
250
|
}.
|
|
205
251
|
|
|
206
252
|
req_row_fully_covered(Row) :-
|
|
@@ -221,10 +267,24 @@ req_row_missing_test(Row) :-
|
|
|
221
267
|
Row.gaps = [missing_test].
|
|
222
268
|
req_row_missing_both(Row) :-
|
|
223
269
|
Row.gaps = [missing_scenario_and_test].
|
|
270
|
+
req_row_proof_proven(Row) :-
|
|
271
|
+
Row.proofStatus == proven.
|
|
272
|
+
req_row_proof_unresolved(Row) :-
|
|
273
|
+
Row.proofStatus == unresolved.
|
|
274
|
+
req_row_proof_missing(Row) :-
|
|
275
|
+
Row.proofStatus == missing.
|
|
276
|
+
req_row_proof_not_applicable(Row) :-
|
|
277
|
+
Row.proofStatus == not_applicable.
|
|
224
278
|
|
|
225
279
|
symbol_row_fully_covered(Row) :-
|
|
226
280
|
CoverageStatus = Row.get(coverageStatus),
|
|
227
281
|
CoverageStatus = fully_covered.
|
|
282
|
+
symbol_row_uncovered(Row) :-
|
|
283
|
+
Row.coverageStatus == uncovered.
|
|
284
|
+
symbol_row_not_applicable(Row) :-
|
|
285
|
+
Row.coverageStatus == not_applicable.
|
|
286
|
+
symbol_row_mixed_role(Row) :-
|
|
287
|
+
Row.traceabilityRole == mixed.
|
|
228
288
|
|
|
229
289
|
count_distinct_targets(Relationship, Id, source, Count) :-
|
|
230
290
|
!,
|
|
@@ -235,7 +295,8 @@ count_distinct_targets(Relationship, Id, source, Count) :-
|
|
|
235
295
|
requirement_test_count(Id, Count) :-
|
|
236
296
|
findall(TestId, kb_relationship(verified_by, Id, TestId), Verified0),
|
|
237
297
|
findall(TestId, kb_relationship(validates, TestId, Id), Validates0),
|
|
238
|
-
|
|
298
|
+
findall(TestId, scenario_requirement_test(Id, TestId), ScenarioTests0),
|
|
299
|
+
append([Verified0, Validates0, ScenarioTests0], Combined0),
|
|
239
300
|
sort(Combined0, Combined),
|
|
240
301
|
length(Combined, Count).
|
|
241
302
|
|
|
@@ -381,6 +442,11 @@ count_direct_tests(Id, Count) :-
|
|
|
381
442
|
sort(TestIds0, TestIds),
|
|
382
443
|
length(TestIds, Count).
|
|
383
444
|
|
|
445
|
+
count_executable_tests(Id, Count) :-
|
|
446
|
+
findall(TestId, kb_relationship(executable_for, Id, TestId), TestIds0),
|
|
447
|
+
sort(TestIds0, TestIds),
|
|
448
|
+
length(TestIds, Count).
|
|
449
|
+
|
|
384
450
|
requirement_gap_list(Id, [Reason]) :-
|
|
385
451
|
coverage_gap(Id, Reason),
|
|
386
452
|
!.
|
|
@@ -399,11 +465,15 @@ requirement_coverage_state(_Id, _Props, [], false, not_applicable).
|
|
|
399
465
|
|
|
400
466
|
filter_req_coverage_rows(true, Rows, Rows).
|
|
401
467
|
filter_req_coverage_rows(false, Rows, Filtered) :-
|
|
402
|
-
exclude(
|
|
468
|
+
exclude(req_row_proof_passing, Rows, Filtered).
|
|
469
|
+
|
|
470
|
+
req_row_proof_passing(Row) :-
|
|
471
|
+
Status = Row.proofStatus,
|
|
472
|
+
memberchk(Status, [proven, not_applicable]).
|
|
403
473
|
|
|
404
474
|
filter_symbol_coverage_rows(true, Rows, Rows).
|
|
405
475
|
filter_symbol_coverage_rows(false, Rows, Filtered) :-
|
|
406
|
-
|
|
476
|
+
include(symbol_row_uncovered, Rows, Filtered).
|
|
407
477
|
|
|
408
478
|
entity_priority(Props, Priority) :-
|
|
409
479
|
( memberchk(priority=PriorityValue, Props)
|
package/src/kb.pl
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
with_kb_mutex/1,
|
|
8
8
|
kb_assert_entity/2,
|
|
9
9
|
kb_assert_entity_no_audit/2,
|
|
10
|
+
kb_commit_upsert/5,
|
|
10
11
|
kb_log_entity_upsert/3,
|
|
11
12
|
kb_retract_entity/1,
|
|
12
13
|
kb_retract_entity/3,
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
predicate_schema/6,
|
|
44
45
|
predicate_fact/5,
|
|
45
46
|
contradicting_reqs/3,
|
|
47
|
+
req_conflict_witness/3,
|
|
46
48
|
check_req_contradiction/1,
|
|
47
49
|
normalize_term_atom/2,
|
|
48
50
|
normalize_term_atom_list/2,
|
|
@@ -111,7 +113,10 @@ kb_attach(Directory) :-
|
|
|
111
113
|
atom_concat(Directory, '/audit.log', AuditLog),
|
|
112
114
|
( db_attached(AuditLog)
|
|
113
115
|
-> true % Already attached
|
|
114
|
-
;
|
|
116
|
+
; % Close the journal stream after each write. The default `flush`
|
|
117
|
+
% mode retains an exclusive write lock for the lifetime of a
|
|
118
|
+
% long-running MCP process and blocks writers in other runtimes.
|
|
119
|
+
db_attach(AuditLog, [sync(close)])
|
|
115
120
|
),
|
|
116
121
|
% Track attachment state
|
|
117
122
|
assert(kb_attached(Directory)),
|
|
@@ -171,12 +176,18 @@ kb_save_locked(Directory) :-
|
|
|
171
176
|
catch(
|
|
172
177
|
(
|
|
173
178
|
ensure_snapshot_current(DataFile),
|
|
179
|
+
kb_stage(snapshot_save),
|
|
174
180
|
save_rdf_snapshot(TempFile),
|
|
181
|
+
kb_stage(audit_sync),
|
|
182
|
+
sync_audit_log,
|
|
183
|
+
% Keep the durable RDF rename after audit synchronization. The
|
|
184
|
+
% temporary snapshot can be discarded if an audit-stage failure
|
|
185
|
+
% occurs, so a failed commit cannot publish new RDF state.
|
|
186
|
+
kb_stage(snapshot_save),
|
|
175
187
|
rename_file(TempFile, DataFile),
|
|
176
188
|
current_data_stamp(DataFile, UpdatedStamp),
|
|
177
189
|
retractall(kb_attached_snapshot(_)),
|
|
178
|
-
assert(kb_attached_snapshot(UpdatedStamp))
|
|
179
|
-
sync_audit_log
|
|
190
|
+
assert(kb_attached_snapshot(UpdatedStamp))
|
|
180
191
|
),
|
|
181
192
|
Error,
|
|
182
193
|
(
|
|
@@ -304,6 +315,109 @@ kb_assert_entity_no_audit(Type, Props) :-
|
|
|
304
315
|
)
|
|
305
316
|
)).
|
|
306
317
|
|
|
318
|
+
%% kb_commit_upsert(+Type, +Properties, +Relationships, +SkipContradiction, -ChangeKind)
|
|
319
|
+
% Commit an entity upsert, its relationships, audit entries, and RDF snapshot
|
|
320
|
+
% as one bounded operation. The branch lock is held before any RDF mutation,
|
|
321
|
+
% so concurrent runtimes cannot observe or publish an intermediate upsert.
|
|
322
|
+
% Relationships use rel(Type, FromId, ToId, Metadata) terms. Metadata is
|
|
323
|
+
% retained for the caller's serialization contract; the audit schema records
|
|
324
|
+
% only the relationship endpoints and type, as before.
|
|
325
|
+
kb_commit_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
326
|
+
memberchk(id=Id, Props),
|
|
327
|
+
memberchk(SkipContradiction, [true, false]),
|
|
328
|
+
kb_attached(Directory),
|
|
329
|
+
atom_concat(Directory, '/kb.rdf', DataFile),
|
|
330
|
+
kb_stage(runtime),
|
|
331
|
+
kb_runtime_diagnostic,
|
|
332
|
+
% Emit the lock marker before waiting so a timeout identifies a branch
|
|
333
|
+
% lock held by another runtime instead of reporting only the prior stage.
|
|
334
|
+
kb_stage(lock),
|
|
335
|
+
with_kb_file_lock(Directory, (
|
|
336
|
+
audit_store_writable,
|
|
337
|
+
ensure_snapshot_current(DataFile),
|
|
338
|
+
upsert_change_kind(Id, ChangeKind),
|
|
339
|
+
rdf_transaction((
|
|
340
|
+
kb_stage(rdf_mutation),
|
|
341
|
+
kb_assert_entity_no_audit(Type, Props),
|
|
342
|
+
kb_commit_relationships_no_audit(Relationships),
|
|
343
|
+
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
344
|
+
kb_stage(entity_audit),
|
|
345
|
+
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
346
|
+
kb_stage(relationship_audit),
|
|
347
|
+
kb_commit_relationship_audits(Relationships),
|
|
348
|
+
kb_save_locked(Directory)
|
|
349
|
+
))
|
|
350
|
+
)).
|
|
351
|
+
|
|
352
|
+
upsert_change_kind(Id, updated) :-
|
|
353
|
+
once(kb_entity(Id, _, _)),
|
|
354
|
+
!.
|
|
355
|
+
upsert_change_kind(_, created).
|
|
356
|
+
|
|
357
|
+
kb_commit_relationships_no_audit([]).
|
|
358
|
+
kb_commit_relationships_no_audit([
|
|
359
|
+
rel(RelType, FromId, ToId, Metadata)|Rest
|
|
360
|
+
]) :-
|
|
361
|
+
kb_assert_relationship_no_audit(RelType, FromId, ToId, Metadata),
|
|
362
|
+
kb_commit_relationships_no_audit(Rest).
|
|
363
|
+
|
|
364
|
+
kb_maybe_check_req_contradiction(req, Id, false) :-
|
|
365
|
+
!,
|
|
366
|
+
kb_stage(contradiction_check),
|
|
367
|
+
check_req_contradiction(Id).
|
|
368
|
+
kb_maybe_check_req_contradiction(_, _, _).
|
|
369
|
+
|
|
370
|
+
kb_commit_relationship_audits([]).
|
|
371
|
+
kb_commit_relationship_audits([
|
|
372
|
+
rel(RelType, FromId, ToId, _Metadata)|Rest
|
|
373
|
+
]) :-
|
|
374
|
+
kb_log_relationship_upsert(RelType, FromId, ToId, []),
|
|
375
|
+
kb_commit_relationship_audits(Rest).
|
|
376
|
+
|
|
377
|
+
% Probe the existing journal without waiting. A process running code from
|
|
378
|
+
% before the sync(close) fix may still retain the journal's write lock; fail
|
|
379
|
+
% before touching RDF so callers receive a safe, retryable diagnostic.
|
|
380
|
+
audit_store_writable :-
|
|
381
|
+
kb_audit_db(AuditLog),
|
|
382
|
+
( exists_file(AuditLog)
|
|
383
|
+
-> catch(
|
|
384
|
+
setup_call_cleanup(
|
|
385
|
+
open(AuditLog, append, Stream,
|
|
386
|
+
[lock(write), wait(false), encoding(utf8)]),
|
|
387
|
+
true,
|
|
388
|
+
close(Stream)
|
|
389
|
+
),
|
|
390
|
+
Error,
|
|
391
|
+
throw(error(permission_error(lock, audit_log, AuditLog),
|
|
392
|
+
context(kb_commit_upsert/5, Error)))
|
|
393
|
+
)
|
|
394
|
+
; true
|
|
395
|
+
).
|
|
396
|
+
|
|
397
|
+
% Reserved stderr markers are consumed by PrologProcess diagnostics and never
|
|
398
|
+
% form part of the JSON result. They make timeout errors stage-specific even
|
|
399
|
+
% when the child is blocked inside a filesystem primitive.
|
|
400
|
+
kb_stage(Stage) :-
|
|
401
|
+
format(user_error, '__KIBI_STAGE__:~w~n', [Stage]),
|
|
402
|
+
flush_output(user_error).
|
|
403
|
+
|
|
404
|
+
kb_runtime_diagnostic :-
|
|
405
|
+
( current_prolog_flag(pid, Pid) -> true ; Pid = unknown ),
|
|
406
|
+
( kb_audit_db(AuditLog) -> true ; AuditLog = unknown ),
|
|
407
|
+
kb_environment_value('KIBI_RUNTIME_NAME', RuntimeName),
|
|
408
|
+
kb_environment_value('KIBI_RUNTIME_VERSION', RuntimeVersion),
|
|
409
|
+
kb_environment_value('KIBI_PACKAGE_VERSIONS', PackageVersions),
|
|
410
|
+
format(user_error,
|
|
411
|
+
'__KIBI_RUNTIME__:pid=~w;audit=~w;runtime=~w@~w;packages=~w~n',
|
|
412
|
+
[Pid, AuditLog, RuntimeName, RuntimeVersion, PackageVersions]),
|
|
413
|
+
flush_output(user_error).
|
|
414
|
+
|
|
415
|
+
kb_environment_value(Name, Value) :-
|
|
416
|
+
( getenv(Name, Value)
|
|
417
|
+
-> true
|
|
418
|
+
; Value = unknown
|
|
419
|
+
).
|
|
420
|
+
|
|
307
421
|
%% kb_retract_entity_relationships(+Id)
|
|
308
422
|
% Remove all relationship triples for an entity, preserving property triples.
|
|
309
423
|
% Used by projectStagedEntities to clear stale relationships before re-asserting.
|
|
@@ -358,7 +472,7 @@ kb_retract_entity(Id, Type, AuditProps) :-
|
|
|
358
472
|
|
|
359
473
|
entity_delete_audit_props(Id, Props, AuditProps) :-
|
|
360
474
|
findall(Key=Value,
|
|
361
|
-
( member(Key, [title, source, text_ref]),
|
|
475
|
+
( member(Key, [title, source, text_ref, semantic_text]),
|
|
362
476
|
memberchk(Key=RawValue, Props),
|
|
363
477
|
audit_property_value(RawValue, Value)
|
|
364
478
|
),
|
|
@@ -398,7 +512,7 @@ kb_entity(Id, Type, Props) :-
|
|
|
398
512
|
atom_concat(BaseURI, type, TypeURI),
|
|
399
513
|
PropURI \= TypeURI,
|
|
400
514
|
uri_to_key(PropURI, Key),
|
|
401
|
-
literal_to_value(ValueLiteral, Value)
|
|
515
|
+
literal_to_value(Key, ValueLiteral, Value)
|
|
402
516
|
), Props).
|
|
403
517
|
|
|
404
518
|
% Fallback: read from legacy entity/4 facts loaded from .pl files
|
|
@@ -625,7 +739,7 @@ store_property(EntityURI, Key, Value, Graph) :-
|
|
|
625
739
|
% Key-aware: typed fact fields get specific XSD datatypes.
|
|
626
740
|
value_to_literal(Key, Value, Literal) :-
|
|
627
741
|
% Typed fact fields with specific XSD datatypes
|
|
628
|
-
( Key
|
|
742
|
+
( integer_property_key(Key), integer(Value)
|
|
629
743
|
-> Literal = Value^^'http://www.w3.org/2001/XMLSchema#integer'
|
|
630
744
|
; Key == value_number, number(Value)
|
|
631
745
|
-> Literal = Value^^'http://www.w3.org/2001/XMLSchema#decimal'
|
|
@@ -643,9 +757,29 @@ value_to_literal(Key, Value, Literal) :-
|
|
|
643
757
|
Literal = Str^^'http://www.w3.org/2001/XMLSchema#string'
|
|
644
758
|
).
|
|
645
759
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
760
|
+
integer_property_key(Key) :-
|
|
761
|
+
memberchk(Key, [
|
|
762
|
+
value_int,
|
|
763
|
+
predicate_arity,
|
|
764
|
+
claim_span_start,
|
|
765
|
+
claim_span_end,
|
|
766
|
+
sourceLine,
|
|
767
|
+
sourceColumn,
|
|
768
|
+
sourceEndLine,
|
|
769
|
+
sourceEndColumn
|
|
770
|
+
]).
|
|
771
|
+
|
|
772
|
+
%% literal_to_value(+Key, +Literal, -Value)
|
|
773
|
+
% Extract a property value from an RDF literal. JSON-backed fields must remain
|
|
774
|
+
% intact: JSON arrays are also valid Prolog list syntax, but parsing them as
|
|
775
|
+
% Prolog terms corrupts their object entries before the JSON decoder sees them.
|
|
776
|
+
literal_to_value(semantic_inventory, Literal, Value) :-
|
|
777
|
+
!,
|
|
778
|
+
structured_literal_value(Literal, Value).
|
|
779
|
+
literal_to_value(verification_receipts, Literal, Value) :-
|
|
780
|
+
!,
|
|
781
|
+
structured_literal_value(Literal, Value).
|
|
782
|
+
literal_to_value(_Key, Literal, Value) :-
|
|
649
783
|
( % Handle ^^/2 functor (RDF typed literal shorthand)
|
|
650
784
|
Literal = ^^(StrVal, 'http://www.w3.org/2001/XMLSchema#string')
|
|
651
785
|
-> ( % Preserve RDF typed literal functor for string values so callers
|
|
@@ -678,6 +812,12 @@ literal_to_value(Literal, Value) :-
|
|
|
678
812
|
; Value = Literal
|
|
679
813
|
).
|
|
680
814
|
|
|
815
|
+
structured_literal_value(^^(Value, _), Value) :- !.
|
|
816
|
+
structured_literal_value(literal(type(_, Value)), Value) :- !.
|
|
817
|
+
structured_literal_value(literal(lang(_, Value)), Value) :- !.
|
|
818
|
+
structured_literal_value(literal(Value), Value) :- !.
|
|
819
|
+
structured_literal_value(Value, Value).
|
|
820
|
+
|
|
681
821
|
%% literal_to_atom(+Literal, -Atom)
|
|
682
822
|
% Convert RDF literal to atom (for type field).
|
|
683
823
|
literal_to_atom(Literal, Atom) :-
|
|
@@ -979,19 +1119,21 @@ current_req(Id) :-
|
|
|
979
1119
|
% semantic conflicts (same subject/property but incompatible values or polarities).
|
|
980
1120
|
% Checks in order of specificity: polarity conflicts, then value conflicts.
|
|
981
1121
|
contradicting_reqs(ReqA, ReqB, Reason) :-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
ReqA @< ReqB,
|
|
985
|
-
req_conflict(ReqA, ReqB, Reason).
|
|
1122
|
+
req_conflict_witness(ReqA, ReqB, Witness),
|
|
1123
|
+
Reason = Witness.reason.
|
|
986
1124
|
|
|
987
1125
|
%% ------------------------------------------------------------------
|
|
988
1126
|
%% Semantic Contradiction Helpers (Task 4)
|
|
989
1127
|
%% ------------------------------------------------------------------
|
|
990
1128
|
|
|
991
|
-
%%
|
|
992
|
-
%
|
|
993
|
-
%
|
|
994
|
-
|
|
1129
|
+
%% req_conflict_witness(+ReqA, +ReqB, -Witness)
|
|
1130
|
+
% Return exact, source-bound evidence for one proven strict or predicate
|
|
1131
|
+
% contradiction. This remains deliberately syntactic: differently shaped
|
|
1132
|
+
% terms are never treated as equivalent merely because their prose is similar.
|
|
1133
|
+
req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
1134
|
+
current_req(ReqA),
|
|
1135
|
+
current_req(ReqB),
|
|
1136
|
+
ReqA @< ReqB,
|
|
995
1137
|
kb_relationship(constrains, ReqA, SubjectFactA),
|
|
996
1138
|
kb_relationship(constrains, ReqB, SubjectFactB),
|
|
997
1139
|
fact_subject_key(SubjectFactA, SubjectKey),
|
|
@@ -1005,8 +1147,23 @@ req_conflict(ReqA, ReqB, Reason) :-
|
|
|
1005
1147
|
OpB, ValTypeB, ValB, UnitB, ScopeB, PolarityB, Reason)
|
|
1006
1148
|
; property_conflict(SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, PolarityA,
|
|
1007
1149
|
OpB, ValTypeB, ValB, UnitB, PolarityB, Reason)
|
|
1008
|
-
)
|
|
1009
|
-
|
|
1150
|
+
),
|
|
1151
|
+
property_conflict_side(ReqA, FactA, SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, ScopeA, PolarityA, ValidFromA, ValidToA, Left),
|
|
1152
|
+
property_conflict_side(ReqB, FactB, SubjectKey, PropertyKey, OpB, ValTypeB, ValB, UnitB, ScopeB, PolarityB, ValidFromB, ValidToB, Right),
|
|
1153
|
+
Witness = _{
|
|
1154
|
+
kind: strict_property,
|
|
1155
|
+
status: contradiction,
|
|
1156
|
+
requirements: [ReqA, ReqB],
|
|
1157
|
+
reason: Reason,
|
|
1158
|
+
subjectKey: SubjectKey,
|
|
1159
|
+
propertyKey: PropertyKey,
|
|
1160
|
+
left: Left,
|
|
1161
|
+
right: Right
|
|
1162
|
+
}.
|
|
1163
|
+
req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
1164
|
+
current_req(ReqA),
|
|
1165
|
+
current_req(ReqB),
|
|
1166
|
+
ReqA @< ReqB,
|
|
1010
1167
|
effective_req_predicate(ReqA, FactA, Namespace, Name, Args, PolarityA),
|
|
1011
1168
|
effective_req_predicate(ReqB, FactB, Namespace, Name, Args, PolarityB),
|
|
1012
1169
|
FactA \= FactB,
|
|
@@ -1016,6 +1173,86 @@ req_conflict(ReqA, ReqB, Reason) :-
|
|
|
1016
1173
|
string(Reason),
|
|
1017
1174
|
"Predicate conflict on ~w:~w(~w): ~w asserts ~w while ~w asserts ~w",
|
|
1018
1175
|
[Namespace, Name, ArgsText, ReqA, PolarityA, ReqB, PolarityB]
|
|
1176
|
+
),
|
|
1177
|
+
predicate_conflict_side(ReqA, FactA, Namespace, Name, Args, PolarityA, Left),
|
|
1178
|
+
predicate_conflict_side(ReqB, FactB, Namespace, Name, Args, PolarityB, Right),
|
|
1179
|
+
length(Args, Arity),
|
|
1180
|
+
findall(SchemaId, predicate_schema(SchemaId, Namespace, Name, Arity, _ArgumentNames, _ArgumentTypes), SchemaIds0),
|
|
1181
|
+
sort(SchemaIds0, SchemaIds),
|
|
1182
|
+
Witness = _{
|
|
1183
|
+
kind: predicate,
|
|
1184
|
+
status: contradiction,
|
|
1185
|
+
requirements: [ReqA, ReqB],
|
|
1186
|
+
reason: Reason,
|
|
1187
|
+
predicateNamespace: Namespace,
|
|
1188
|
+
predicateName: Name,
|
|
1189
|
+
predicateArgs: Args,
|
|
1190
|
+
schemaIds: SchemaIds,
|
|
1191
|
+
left: Left,
|
|
1192
|
+
right: Right
|
|
1193
|
+
}.
|
|
1194
|
+
|
|
1195
|
+
property_conflict_side(ReqId, FactId, SubjectKey, PropertyKey, Operator, ValueType, Value, Unit, Scope, Polarity, ValidFrom, ValidTo, Side) :-
|
|
1196
|
+
entity_evidence_source(ReqId, req, RequirementSource),
|
|
1197
|
+
fact_evidence_fields(FactId, FactSource, ClaimKey, ClaimText, _CanonicalKey),
|
|
1198
|
+
Side = _{
|
|
1199
|
+
requirementId: ReqId,
|
|
1200
|
+
requirementSource: RequirementSource,
|
|
1201
|
+
factId: FactId,
|
|
1202
|
+
factSource: FactSource,
|
|
1203
|
+
claimKey: ClaimKey,
|
|
1204
|
+
claimText: ClaimText,
|
|
1205
|
+
term: _{
|
|
1206
|
+
subjectKey: SubjectKey,
|
|
1207
|
+
propertyKey: PropertyKey,
|
|
1208
|
+
operator: Operator,
|
|
1209
|
+
valueType: ValueType,
|
|
1210
|
+
value: Value,
|
|
1211
|
+
unit: Unit,
|
|
1212
|
+
scope: Scope,
|
|
1213
|
+
polarity: Polarity,
|
|
1214
|
+
validFrom: ValidFrom,
|
|
1215
|
+
validTo: ValidTo
|
|
1216
|
+
}
|
|
1217
|
+
}.
|
|
1218
|
+
|
|
1219
|
+
predicate_conflict_side(ReqId, FactId, Namespace, Name, Args, Polarity, Side) :-
|
|
1220
|
+
entity_evidence_source(ReqId, req, RequirementSource),
|
|
1221
|
+
fact_evidence_fields(FactId, FactSource, ClaimKey, ClaimText, CanonicalKey),
|
|
1222
|
+
Side = _{
|
|
1223
|
+
requirementId: ReqId,
|
|
1224
|
+
requirementSource: RequirementSource,
|
|
1225
|
+
factId: FactId,
|
|
1226
|
+
factSource: FactSource,
|
|
1227
|
+
claimKey: ClaimKey,
|
|
1228
|
+
claimText: ClaimText,
|
|
1229
|
+
term: _{
|
|
1230
|
+
namespace: Namespace,
|
|
1231
|
+
name: Name,
|
|
1232
|
+
args: Args,
|
|
1233
|
+
polarity: Polarity,
|
|
1234
|
+
canonicalKey: CanonicalKey
|
|
1235
|
+
}
|
|
1236
|
+
}.
|
|
1237
|
+
|
|
1238
|
+
entity_evidence_source(EntityId, Type, Source) :-
|
|
1239
|
+
kb_entity(EntityId, Type, Props),
|
|
1240
|
+
(memberchk(source=RawSource, Props) -> evidence_term_atom(RawSource, Source) ; Source = '').
|
|
1241
|
+
|
|
1242
|
+
fact_evidence_fields(FactId, Source, ClaimKey, ClaimText, CanonicalKey) :-
|
|
1243
|
+
kb_entity(FactId, fact, Props),
|
|
1244
|
+
(memberchk(source=RawSource, Props) -> evidence_term_atom(RawSource, Source) ; Source = ''),
|
|
1245
|
+
(memberchk(claim_key=RawClaimKey, Props) -> evidence_term_atom(RawClaimKey, ClaimKey) ; ClaimKey = ''),
|
|
1246
|
+
(memberchk(claim_text=RawClaimText, Props) -> evidence_term_atom(RawClaimText, ClaimText) ; ClaimText = ''),
|
|
1247
|
+
(memberchk(canonical_key=RawCanonicalKey, Props) -> evidence_term_atom(RawCanonicalKey, CanonicalKey) ; CanonicalKey = '').
|
|
1248
|
+
|
|
1249
|
+
evidence_term_atom(Raw, Atom) :-
|
|
1250
|
+
unwrap_rdf_value(Raw, Value),
|
|
1251
|
+
( atom(Value)
|
|
1252
|
+
-> Atom = Value
|
|
1253
|
+
; string(Value)
|
|
1254
|
+
-> atom_string(Atom, Value)
|
|
1255
|
+
; term_string(Value, String), atom_string(Atom, String)
|
|
1019
1256
|
).
|
|
1020
1257
|
|
|
1021
1258
|
%% effective_req_predicate(+ReqId, -FactId, -Namespace, -Name, -Args, -Polarity)
|