kibi-core 0.13.0 → 0.14.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 +3 -0
- package/schema/relationships.pl +8 -0
- package/schema/validation.pl +2 -0
- package/src/checks.pl +69 -36
- package/src/discovery.pl +42 -7
- package/src/kb.pl +328 -18
- package/src/requirement_proof.pl +55 -5
- package/src/rule_registry.pl +40 -0
- package/src/semantic_quality.pl +750 -0
- package/src/status.pl +51 -16
- package/src/units.pl +146 -0
package/src/kb.pl
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
kb_assert_entity/2,
|
|
15
15
|
kb_assert_entity_no_audit/2,
|
|
16
16
|
kb_commit_upsert/5,
|
|
17
|
+
kb_commit_upsert_batch/2,
|
|
17
18
|
kb_log_entity_upsert/3,
|
|
18
19
|
kb_retract_entity/1,
|
|
19
20
|
kb_retract_entity/3,
|
|
@@ -24,10 +25,13 @@
|
|
|
24
25
|
kb_indexed_sources/1,
|
|
25
26
|
kb_query_entities/8,
|
|
26
27
|
kb_search_entities/6,
|
|
28
|
+
kb_list_search_candidates/5,
|
|
29
|
+
kb_entity_ids/1,
|
|
27
30
|
kb_rebuild_indexes/0,
|
|
28
31
|
kb_entity/3,
|
|
29
32
|
kb_entities_by_source/2,
|
|
30
33
|
kb_assert_relationship/4,
|
|
34
|
+
kb_query_proof_contracts/4,
|
|
31
35
|
kb_assert_relationship_no_audit/4,
|
|
32
36
|
kb_log_relationship_upsert/4,
|
|
33
37
|
kb_relationship/3,
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
symbol_no_req_coverage/2,
|
|
57
61
|
predicate_schema/6,
|
|
58
62
|
predicate_fact/5,
|
|
63
|
+
canonical_property_tuple/9,
|
|
59
64
|
contradicting_reqs/3,
|
|
60
65
|
req_conflict_witness/3,
|
|
61
66
|
check_req_contradiction/1,
|
|
@@ -71,12 +76,15 @@
|
|
|
71
76
|
:- use_module(library(thread)).
|
|
72
77
|
:- use_module(library(filesex)).
|
|
73
78
|
:- use_module(library(readutil)).
|
|
79
|
+
:- use_module(library(sha), [sha_hash/3, hash_atom/2]).
|
|
74
80
|
:- use_module(library(http/json)).
|
|
75
81
|
:- use_module(library(aggregate), [aggregate_all/3]).
|
|
76
82
|
:- use_module(library(lists), [sum_list/2]).
|
|
77
83
|
:- use_module(library(ordsets)).
|
|
84
|
+
:- use_module(library(solution_sequences), [distinct/1, limit/2, offset/2, order_by/2]).
|
|
78
85
|
:- use_module('../schema/entities.pl', [entity_type/1, entity_property/3, required_property/2]).
|
|
79
86
|
:- use_module('../schema/relationships.pl', [relationship_type/1, valid_relationship/3]).
|
|
87
|
+
:- use_module('units.pl', [canonical_quantity/6]).
|
|
80
88
|
:- use_module('../schema/validation.pl', [validate_entity/2, validate_relationship/3]).
|
|
81
89
|
|
|
82
90
|
% Constants
|
|
@@ -117,6 +125,13 @@ kb_uri('urn-kibi:').
|
|
|
117
125
|
:- dynamic kb_index_symbol_coordinate/5.
|
|
118
126
|
:- dynamic kb_index_entity_count/1.
|
|
119
127
|
:- dynamic kb_index_triple_count/1.
|
|
128
|
+
% Read memo for per-entity property lists, valid only for the recorded
|
|
129
|
+
% Graph-rdf_generation key. rdf_generation/1 changes on every RDF
|
|
130
|
+
% modification (including inside transactions) and reverts on rollback, so
|
|
131
|
+
% the memo never serves a property list the store no longer holds.
|
|
132
|
+
:- dynamic kb_entity_props_memo/2.
|
|
133
|
+
:- dynamic kb_entity_props_memo_key/1.
|
|
134
|
+
:- dynamic kb_index_verified_key/1.
|
|
120
135
|
:- dynamic entity/4. % Support legacy .pl file format (Type, Id, Title, Props)
|
|
121
136
|
|
|
122
137
|
%% kb_attach(+Directory)
|
|
@@ -187,8 +202,22 @@ kb_attach_journaled(Directory) :-
|
|
|
187
202
|
load_kb_pl_files(Directory),
|
|
188
203
|
kb_rebuild_indexes.
|
|
189
204
|
|
|
205
|
+
% SWI-Prolog's rdf_db fails to write journals ("invalid term_t ... out of
|
|
206
|
+
% range") for graphs whose URI is roughly 230 characters or longer. Store
|
|
207
|
+
% paths inside deep workspaces reach that length, so those stores use a short
|
|
208
|
+
% digest-based graph URI; shorter paths keep the file:// URI they always had.
|
|
209
|
+
journal_expected_graph_uri(Directory, Expected) :-
|
|
210
|
+
atom_concat('file://', Directory, FileURI),
|
|
211
|
+
atom_length(FileURI, Length),
|
|
212
|
+
( Length =< 200
|
|
213
|
+
-> Expected = FileURI
|
|
214
|
+
; sha_hash(Directory, Hash, [algorithm(sha1)]),
|
|
215
|
+
hash_atom(Hash, Digest),
|
|
216
|
+
atom_concat('urn:kibi:store:', Digest, Expected)
|
|
217
|
+
).
|
|
218
|
+
|
|
190
219
|
journal_graph_uri(Directory, GraphURI) :-
|
|
191
|
-
|
|
220
|
+
journal_expected_graph_uri(Directory, Expected),
|
|
192
221
|
( rdf_graph(Expected)
|
|
193
222
|
-> GraphURI = Expected
|
|
194
223
|
; findall(G,
|
|
@@ -259,15 +288,16 @@ kb_write_lock_owner(Directory) :-
|
|
|
259
288
|
; BootId = ""
|
|
260
289
|
),
|
|
261
290
|
get_time(Now),
|
|
262
|
-
|
|
291
|
+
stamp_date_time(Now, NowUTC, 'UTC'),
|
|
292
|
+
format_time(atom(StartedAt), '%FT%TZ', NowUTC),
|
|
263
293
|
atom_concat(Path, '.tmp', TmpPath),
|
|
264
294
|
setup_call_cleanup(
|
|
265
295
|
open(TmpPath, write, Stream),
|
|
266
296
|
json_write_dict(Stream,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
_{pid:Pid,
|
|
298
|
+
workspaceRoot:Directory,
|
|
299
|
+
bootId:BootId,
|
|
300
|
+
startedAt:StartedAt},
|
|
271
301
|
[]),
|
|
272
302
|
(close(Stream), rename_file(TmpPath, Path))).
|
|
273
303
|
|
|
@@ -324,6 +354,9 @@ kb_detach :-
|
|
|
324
354
|
retractall(kb_dirty),
|
|
325
355
|
retractall(kb_index_ready),
|
|
326
356
|
retractall(kb_index_dirty),
|
|
357
|
+
retractall(kb_index_verified_key(_)),
|
|
358
|
+
retractall(kb_entity_props_memo(_, _)),
|
|
359
|
+
retractall(kb_entity_props_memo_key(_)),
|
|
327
360
|
retractall(kb_index_entity(_, _)),
|
|
328
361
|
retractall(kb_index_type(_, _)),
|
|
329
362
|
retractall(kb_index_tag(_, _)),
|
|
@@ -1089,6 +1122,62 @@ kb_maybe_check_req_contradiction(req, Id, false) :-
|
|
|
1089
1122
|
check_req_contradiction(Id).
|
|
1090
1123
|
kb_maybe_check_req_contradiction(_, _, _).
|
|
1091
1124
|
|
|
1125
|
+
% Stage one upsert inside an already-open transaction. The caller saves once.
|
|
1126
|
+
kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
1127
|
+
memberchk(id=Id, Props),
|
|
1128
|
+
memberchk(SkipContradiction, [true, false]),
|
|
1129
|
+
upsert_change_kind(Id, ChangeKind),
|
|
1130
|
+
kb_assert_entity_no_audit(Type, Props),
|
|
1131
|
+
kb_commit_relationships_no_audit(Relationships),
|
|
1132
|
+
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
1133
|
+
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1134
|
+
kb_commit_relationship_audits(Relationships).
|
|
1135
|
+
|
|
1136
|
+
kb_stage_upserts([], []).
|
|
1137
|
+
kb_stage_upserts([
|
|
1138
|
+
upsert(Type, Props, Relationships, SkipContradiction)|Rest
|
|
1139
|
+
], [ChangeKind|Kinds]) :-
|
|
1140
|
+
kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind),
|
|
1141
|
+
kb_stage_upserts(Rest, Kinds).
|
|
1142
|
+
|
|
1143
|
+
%% kb_commit_upsert_batch(+Entries, -ChangeKinds)
|
|
1144
|
+
% implements REQ-core-atomic-upsert-persistence
|
|
1145
|
+
% Commit many upserts in one RDF transaction and one journal flush.
|
|
1146
|
+
% Entries are upsert(Type, Props, Relationships, SkipContradiction) terms.
|
|
1147
|
+
kb_commit_upsert_batch(Entries, ChangeKinds) :-
|
|
1148
|
+
kb_storage_mode(journaled),
|
|
1149
|
+
!,
|
|
1150
|
+
with_kb_mutex((
|
|
1151
|
+
( kb_dirty -> WasDirty = true ; WasDirty = false ),
|
|
1152
|
+
catch(
|
|
1153
|
+
rdf_transaction((
|
|
1154
|
+
kb_stage(rdf_mutation),
|
|
1155
|
+
kb_stage_upserts(Entries, ChangeKinds)
|
|
1156
|
+
)),
|
|
1157
|
+
Error,
|
|
1158
|
+
( (WasDirty == false -> retractall(kb_dirty) ; true),
|
|
1159
|
+
throw(Error)
|
|
1160
|
+
)
|
|
1161
|
+
),
|
|
1162
|
+
kb_mark_dirty,
|
|
1163
|
+
kb_save_journaled
|
|
1164
|
+
)).
|
|
1165
|
+
kb_commit_upsert_batch(Entries, ChangeKinds) :-
|
|
1166
|
+
kb_attached(Directory),
|
|
1167
|
+
atom_concat(Directory, '/kb.rdf', DataFile),
|
|
1168
|
+
kb_stage(runtime),
|
|
1169
|
+
kb_runtime_diagnostic,
|
|
1170
|
+
kb_stage(lock),
|
|
1171
|
+
with_kb_file_lock(Directory, (
|
|
1172
|
+
audit_store_writable,
|
|
1173
|
+
ensure_snapshot_current(DataFile),
|
|
1174
|
+
rdf_transaction((
|
|
1175
|
+
kb_stage(rdf_mutation),
|
|
1176
|
+
kb_stage_upserts(Entries, ChangeKinds),
|
|
1177
|
+
kb_save_locked(Directory)
|
|
1178
|
+
))
|
|
1179
|
+
)).
|
|
1180
|
+
|
|
1092
1181
|
kb_commit_relationship_audits([]).
|
|
1093
1182
|
kb_commit_relationship_audits([
|
|
1094
1183
|
rel(RelType, FromId, ToId, _Metadata)|Rest
|
|
@@ -1324,6 +1413,42 @@ kb_entity(Id, Type, Props) :-
|
|
|
1324
1413
|
% Raw RDF/entity facts are kept separate from the index wrapper. Rebuilding
|
|
1325
1414
|
% an index must never recurse through the indexed query path.
|
|
1326
1415
|
kb_entity_raw(Id, Type, Props) :-
|
|
1416
|
+
( nonvar(Id)
|
|
1417
|
+
-> rdf_entity_solutions(Id, Solutions),
|
|
1418
|
+
member(Type-Props, Solutions)
|
|
1419
|
+
; kb_entity_raw_rdf(Id, Type, Props)
|
|
1420
|
+
).
|
|
1421
|
+
|
|
1422
|
+
% Fallback: read from legacy entity/4 facts loaded from .pl files
|
|
1423
|
+
kb_entity_raw(Id, Type, Props) :-
|
|
1424
|
+
entity(Type, Id, _Title, PropList),
|
|
1425
|
+
convert_legacy_props(PropList, Props).
|
|
1426
|
+
|
|
1427
|
+
%% rdf_entity_solutions(+Id, -Solutions)
|
|
1428
|
+
% Coverage, proof, and check predicates look up the same entities tens of
|
|
1429
|
+
% thousands of times, and every lookup materializes and decodes every
|
|
1430
|
+
% property (including receipt histories). Memoize the decoded Type-Props
|
|
1431
|
+
% solutions per entity for the current graph and RDF generation.
|
|
1432
|
+
rdf_entity_solutions(Id, Solutions) :-
|
|
1433
|
+
( kb_graph(Graph)
|
|
1434
|
+
-> rdf_generation(Generation),
|
|
1435
|
+
Key = Graph-Generation,
|
|
1436
|
+
( kb_entity_props_memo_key(Current),
|
|
1437
|
+
Current == Key
|
|
1438
|
+
-> true
|
|
1439
|
+
; retractall(kb_entity_props_memo(_, _)),
|
|
1440
|
+
retractall(kb_entity_props_memo_key(_)),
|
|
1441
|
+
assertz(kb_entity_props_memo_key(Key))
|
|
1442
|
+
),
|
|
1443
|
+
( kb_entity_props_memo(Id, Cached)
|
|
1444
|
+
-> Solutions = Cached
|
|
1445
|
+
; findall(Type-Props, kb_entity_raw_rdf(Id, Type, Props), Solutions),
|
|
1446
|
+
assertz(kb_entity_props_memo(Id, Solutions))
|
|
1447
|
+
)
|
|
1448
|
+
; Solutions = []
|
|
1449
|
+
).
|
|
1450
|
+
|
|
1451
|
+
kb_entity_raw_rdf(Id, Type, Props) :-
|
|
1327
1452
|
kb_graph(Graph),
|
|
1328
1453
|
% Find entity by pattern - use unquoted namespace term kb:type
|
|
1329
1454
|
( var(Id)
|
|
@@ -1344,20 +1469,37 @@ kb_entity_raw(Id, Type, Props) :-
|
|
|
1344
1469
|
literal_to_value(Key, ValueLiteral, Value)
|
|
1345
1470
|
), Props).
|
|
1346
1471
|
|
|
1347
|
-
%
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1472
|
+
% Verifying the index counts every type triple in the store. Remember the
|
|
1473
|
+
% inputs of the last successful check (graph, RDF generation, legacy fact
|
|
1474
|
+
% count, and the index's own entity count) and skip the recount while all of
|
|
1475
|
+
% them are unchanged: the recount would then return the same answer. The
|
|
1476
|
+
% index count must be part of the key because incremental index updates are
|
|
1477
|
+
% plain Prolog facts that an RDF rollback does not undo, while the RDF
|
|
1478
|
+
% generation does revert.
|
|
1352
1479
|
kb_ensure_indexes :-
|
|
1353
1480
|
( kb_index_ready,
|
|
1354
1481
|
\+ kb_index_dirty,
|
|
1355
1482
|
kb_index_entity_count(Expected),
|
|
1356
|
-
|
|
1483
|
+
index_verification_key(Expected, Key),
|
|
1484
|
+
( kb_index_verified_key(Verified),
|
|
1485
|
+
Verified == Key
|
|
1486
|
+
-> true
|
|
1487
|
+
; current_entity_index_count(Expected),
|
|
1488
|
+
retractall(kb_index_verified_key(_)),
|
|
1489
|
+
assertz(kb_index_verified_key(Key))
|
|
1490
|
+
)
|
|
1357
1491
|
-> true
|
|
1358
1492
|
; kb_rebuild_indexes
|
|
1359
1493
|
).
|
|
1360
1494
|
|
|
1495
|
+
index_verification_key(IndexCount, key(Graph, Generation, LegacyCount, IndexCount)) :-
|
|
1496
|
+
(kb_graph(Graph) -> true ; Graph = none),
|
|
1497
|
+
rdf_generation(Generation),
|
|
1498
|
+
( predicate_property(kb:entity(_, _, _, _), number_of_clauses(Count))
|
|
1499
|
+
-> LegacyCount = Count
|
|
1500
|
+
; LegacyCount = 0
|
|
1501
|
+
).
|
|
1502
|
+
|
|
1361
1503
|
% Rebuild all acceleration structures from RDF/entity facts. This is
|
|
1362
1504
|
% intentionally disposable: no query result is written back to RDF when an
|
|
1363
1505
|
% index is stale or externally invalidated.
|
|
@@ -1380,6 +1522,7 @@ kb_rebuild_indexes :-
|
|
|
1380
1522
|
assertz(kb_index_triple_count(TripleCount)),
|
|
1381
1523
|
retractall(kb_index_dirty),
|
|
1382
1524
|
retractall(kb_index_ready),
|
|
1525
|
+
retractall(kb_index_verified_key(_)),
|
|
1383
1526
|
assertz(kb_index_ready)
|
|
1384
1527
|
)).
|
|
1385
1528
|
|
|
@@ -1515,6 +1658,97 @@ kb_query_entities(TypeFilter, IdFilter, Tags, SourceFilter, Limit, Offset, Rows,
|
|
|
1515
1658
|
kb_entity(Id, Type, Props) ),
|
|
1516
1659
|
Rows).
|
|
1517
1660
|
|
|
1661
|
+
%% kb_query_proof_contracts(+Id, +Limit, +Offset, -Rows)
|
|
1662
|
+
% Page proof-campaign candidates while reading only contract and binding RDF
|
|
1663
|
+
% properties. In particular, do not call kb_entity/3 here: that materializes
|
|
1664
|
+
% complete entity properties, including potentially large receipt histories.
|
|
1665
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
1666
|
+
kb_query_proof_contracts(IdFilter, Limit, Offset, Rows) :-
|
|
1667
|
+
integer(Limit),
|
|
1668
|
+
integer(Offset),
|
|
1669
|
+
Limit >= 0,
|
|
1670
|
+
Offset >= 0,
|
|
1671
|
+
findall([Id, test, Projected],
|
|
1672
|
+
( limit(Limit,
|
|
1673
|
+
offset(Offset,
|
|
1674
|
+
order_by([asc(Id)],
|
|
1675
|
+
distinct(proof_contract_candidate(IdFilter, Id))))),
|
|
1676
|
+
proof_contract_properties(Id, Contract, Bindings, Source),
|
|
1677
|
+
optional_projected_property(proof_bindings, Bindings,
|
|
1678
|
+
BindingProps),
|
|
1679
|
+
optional_projected_property(source, Source, SourceProps),
|
|
1680
|
+
append([[id=Id, proof_contract=Contract],
|
|
1681
|
+
BindingProps, SourceProps],
|
|
1682
|
+
Projected) ),
|
|
1683
|
+
Rows).
|
|
1684
|
+
|
|
1685
|
+
optional_projected_property(_Key, Value, []) :-
|
|
1686
|
+
var(Value),
|
|
1687
|
+
!.
|
|
1688
|
+
optional_projected_property(Key, Value, [Key=Value]).
|
|
1689
|
+
|
|
1690
|
+
proof_contract_properties(Id, Contract, Bindings) :-
|
|
1691
|
+
proof_contract_properties(Id, Contract, Bindings, _Source).
|
|
1692
|
+
|
|
1693
|
+
% Source is projected alongside the contract so per-contract receipt binding
|
|
1694
|
+
% can hash the authored document without materializing receipt histories.
|
|
1695
|
+
proof_contract_properties(Id, Contract, Bindings, Source) :-
|
|
1696
|
+
( ( kb_graph(Graph),
|
|
1697
|
+
entity_id_to_uri(Id, EntityURI),
|
|
1698
|
+
rdf(EntityURI, kb:type, TypeLiteral, Graph),
|
|
1699
|
+
literal_to_atom(TypeLiteral, test),
|
|
1700
|
+
kb_property_uri(proof_contract, ContractURI),
|
|
1701
|
+
rdf(EntityURI, ContractURI, ContractLiteral, Graph)
|
|
1702
|
+
)
|
|
1703
|
+
-> literal_to_value(proof_contract, ContractLiteral, Contract),
|
|
1704
|
+
( kb_property_uri(proof_bindings, BindingsURI),
|
|
1705
|
+
rdf(EntityURI, BindingsURI, BindingsLiteral, Graph)
|
|
1706
|
+
-> literal_to_value(proof_bindings, BindingsLiteral, Bindings)
|
|
1707
|
+
; true
|
|
1708
|
+
),
|
|
1709
|
+
( kb_property_uri(source, SourceURI),
|
|
1710
|
+
rdf(EntityURI, SourceURI, SourceLiteral, Graph)
|
|
1711
|
+
-> literal_to_value(source, SourceLiteral, Source)
|
|
1712
|
+
; true
|
|
1713
|
+
)
|
|
1714
|
+
; entity(test, Id, _, Properties),
|
|
1715
|
+
memberchk(proof_contract=Contract, Properties),
|
|
1716
|
+
( memberchk(proof_bindings=Bindings, Properties)
|
|
1717
|
+
-> true
|
|
1718
|
+
; true
|
|
1719
|
+
),
|
|
1720
|
+
( memberchk(source=Source, Properties)
|
|
1721
|
+
-> true
|
|
1722
|
+
; true
|
|
1723
|
+
)
|
|
1724
|
+
).
|
|
1725
|
+
|
|
1726
|
+
%% kb_property_uri(+Key, -URI)
|
|
1727
|
+
% Property predicates are asserted in-session as the prefixed atom 'kb:Key',
|
|
1728
|
+
% but a store reloaded from its snapshot carries the expanded 'urn-kibi:Key'
|
|
1729
|
+
% form. Direct RDF lookups must accept both (as uri_to_key/2 already does),
|
|
1730
|
+
% or projections silently miss every entity of a reloaded store.
|
|
1731
|
+
kb_property_uri(Key, URI) :-
|
|
1732
|
+
kb_uri(BaseURI),
|
|
1733
|
+
atom_concat(BaseURI, Key, URI).
|
|
1734
|
+
kb_property_uri(Key, URI) :-
|
|
1735
|
+
atom_concat('kb:', Key, URI).
|
|
1736
|
+
|
|
1737
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
1738
|
+
proof_contract_candidate(none, Id) :-
|
|
1739
|
+
kb_graph(Graph),
|
|
1740
|
+
kb_property_uri(proof_contract, ContractURI),
|
|
1741
|
+
rdf(EntityURI, ContractURI, _, Graph),
|
|
1742
|
+
entity_uri_to_id(EntityURI, Id),
|
|
1743
|
+
rdf(EntityURI, kb:type, TypeLiteral, Graph),
|
|
1744
|
+
literal_to_atom(TypeLiteral, test).
|
|
1745
|
+
proof_contract_candidate(none, Id) :-
|
|
1746
|
+
entity(test, Id, _, Properties),
|
|
1747
|
+
memberchk(proof_contract=_, Properties).
|
|
1748
|
+
proof_contract_candidate(some(Id), Id) :-
|
|
1749
|
+
atom(Id),
|
|
1750
|
+
proof_contract_properties(Id, _, _).
|
|
1751
|
+
|
|
1518
1752
|
indexed_entity_match(TypeFilter, IdFilter, Tags, SourceFilter, Id) :-
|
|
1519
1753
|
( IdFilter == none
|
|
1520
1754
|
-> kb_index_entity(Id, _)
|
|
@@ -1573,10 +1807,74 @@ kb_search_entities(TypeFilter, Query, Limit, Offset, Rows, Count) :-
|
|
|
1573
1807
|
length(Ids, Count),
|
|
1574
1808
|
drop_index_ids(Offset, Ids, Remaining),
|
|
1575
1809
|
take_index_ids(Limit, Remaining, PageIds),
|
|
1576
|
-
|
|
1577
|
-
|
|
1810
|
+
search_candidate_rows(PageIds, Rows).
|
|
1811
|
+
|
|
1812
|
+
%% kb_list_search_candidates(+Type, +Limit, +Offset, -Rows, -Count)
|
|
1813
|
+
% Page every indexed entity (optionally of one type) as projected search
|
|
1814
|
+
% candidate rows, ordered by id. Semantic discovery uses it when no lexical
|
|
1815
|
+
% token can pre-filter the candidate set.
|
|
1816
|
+
kb_list_search_candidates(TypeFilter, Limit, Offset, Rows, Count) :-
|
|
1817
|
+
integer(Limit),
|
|
1818
|
+
integer(Offset),
|
|
1819
|
+
Limit >= 0,
|
|
1820
|
+
Offset >= 0,
|
|
1821
|
+
kb_ensure_indexes,
|
|
1822
|
+
findall(Id,
|
|
1823
|
+
( kb_index_entity(Id, _),
|
|
1824
|
+
(TypeFilter == none -> true ; kb_index_type(TypeFilter, Id)) ),
|
|
1825
|
+
RawIds),
|
|
1826
|
+
sort(RawIds, Ids),
|
|
1827
|
+
length(Ids, Count),
|
|
1828
|
+
drop_index_ids(Offset, Ids, Remaining),
|
|
1829
|
+
take_index_ids(Limit, Remaining, PageIds),
|
|
1830
|
+
search_candidate_rows(PageIds, Rows).
|
|
1831
|
+
|
|
1832
|
+
%% kb_entity_ids(-Ids)
|
|
1833
|
+
% Every entity id, without materializing any properties.
|
|
1834
|
+
kb_entity_ids(Ids) :-
|
|
1835
|
+
kb_ensure_indexes,
|
|
1836
|
+
findall(Id, kb_index_entity(Id, _), RawIds),
|
|
1837
|
+
sort(RawIds, Ids).
|
|
1838
|
+
|
|
1839
|
+
% Search candidates carry only the properties ranking, snippets, and summary
|
|
1840
|
+
% output read. Large structured properties (receipt histories, proof
|
|
1841
|
+
% contracts, semantic inventories, rule IR) stay in the store: a candidate
|
|
1842
|
+
% page of receipt-bearing tests otherwise exceeds the bounded engine output.
|
|
1843
|
+
% Callers that need complete entities reload the final page by id.
|
|
1844
|
+
search_candidate_rows(PageIds, Rows) :-
|
|
1845
|
+
findall([Id, Type, Projected],
|
|
1846
|
+
( member(Id, PageIds),
|
|
1847
|
+
kb_entity(Id, Type, Props),
|
|
1848
|
+
include(search_candidate_property, Props, Projected) ),
|
|
1578
1849
|
Rows).
|
|
1579
1850
|
|
|
1851
|
+
search_candidate_property(Key=_) :-
|
|
1852
|
+
search_candidate_key(Key).
|
|
1853
|
+
|
|
1854
|
+
search_candidate_key(id).
|
|
1855
|
+
search_candidate_key(title).
|
|
1856
|
+
search_candidate_key(status).
|
|
1857
|
+
search_candidate_key(priority).
|
|
1858
|
+
search_candidate_key(severity).
|
|
1859
|
+
search_candidate_key(owner).
|
|
1860
|
+
search_candidate_key(tags).
|
|
1861
|
+
search_candidate_key(source).
|
|
1862
|
+
search_candidate_key(sourceFile).
|
|
1863
|
+
search_candidate_key(sourceLine).
|
|
1864
|
+
search_candidate_key(sourceColumn).
|
|
1865
|
+
search_candidate_key(sourceEndLine).
|
|
1866
|
+
search_candidate_key(sourceEndColumn).
|
|
1867
|
+
search_candidate_key(source_line).
|
|
1868
|
+
search_candidate_key(source_end_line).
|
|
1869
|
+
search_candidate_key(updated_at).
|
|
1870
|
+
search_candidate_key(created_at).
|
|
1871
|
+
search_candidate_key(semantic_text).
|
|
1872
|
+
search_candidate_key(text_ref).
|
|
1873
|
+
search_candidate_key(body).
|
|
1874
|
+
search_candidate_key(content).
|
|
1875
|
+
search_candidate_key(markdownBody).
|
|
1876
|
+
search_candidate_key(markdown_body).
|
|
1877
|
+
|
|
1580
1878
|
indexed_token_match(QueryToken, Id) :-
|
|
1581
1879
|
kb_index_token(IndexedToken, Id),
|
|
1582
1880
|
sub_atom(IndexedToken, _, _, _, QueryToken),
|
|
@@ -2269,10 +2567,14 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
|
2269
2567
|
FactA \= FactB,
|
|
2270
2568
|
scope_intersects(ScopeA, ScopeB),
|
|
2271
2569
|
intervals_overlap(ValidFromA, ValidToA, ValidFromB, ValidToB),
|
|
2272
|
-
(
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2570
|
+
% Compare canonical quantities (30 min == 1800 s); witnesses keep the
|
|
2571
|
+
% authored values so evidence still points at the source text.
|
|
2572
|
+
canonical_quantity(ValTypeA, ValA, UnitA, CanonTypeA, CanonValA, CanonUnitA),
|
|
2573
|
+
canonical_quantity(ValTypeB, ValB, UnitB, CanonTypeB, CanonValB, CanonUnitB),
|
|
2574
|
+
( polarity_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, ScopeA, PolarityA,
|
|
2575
|
+
OpB, CanonTypeB, CanonValB, CanonUnitB, ScopeB, PolarityB, Reason)
|
|
2576
|
+
; property_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, PolarityA,
|
|
2577
|
+
OpB, CanonTypeB, CanonValB, CanonUnitB, PolarityB, Reason)
|
|
2276
2578
|
),
|
|
2277
2579
|
property_conflict_side(ReqA, FactA, SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, ScopeA, PolarityA, ValidFromA, ValidToA, Left),
|
|
2278
2580
|
property_conflict_side(ReqB, FactB, SubjectKey, PropertyKey, OpB, ValTypeB, ValB, UnitB, ScopeB, PolarityB, ValidFromB, ValidToB, Right),
|
|
@@ -2418,6 +2720,14 @@ fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope,
|
|
|
2418
2720
|
( memberchk(scope=ScopeRaw, Props) -> normalize_term_atom(ScopeRaw, Scope) ; Scope = '' ),
|
|
2419
2721
|
( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = require ).
|
|
2420
2722
|
|
|
2723
|
+
%% canonical_property_tuple(+FactId, -Subject, -Property, -Op, -ValType, -Value, -Unit, -Scope, -Polarity)
|
|
2724
|
+
% fact_property_tuple/9 with the quantity converted to its canonical base unit
|
|
2725
|
+
% (units.pl). Used only for comparison; the authored fact is never rewritten.
|
|
2726
|
+
% implements REQ-kibi-unit-canonicalization
|
|
2727
|
+
canonical_property_tuple(FactId, Subject, Property, Op, CanonType, CanonValue, CanonUnit, Scope, Polarity) :-
|
|
2728
|
+
fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope, Polarity),
|
|
2729
|
+
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit).
|
|
2730
|
+
|
|
2421
2731
|
%% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
|
|
2422
2732
|
% Read one project-local ontology predicate schema fact.
|
|
2423
2733
|
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :-
|
package/src/requirement_proof.pl
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
requirement_proof/4
|
|
9
9
|
]).
|
|
10
10
|
|
|
11
|
+
% Source contains non-ASCII text; do not depend on the host locale.
|
|
12
|
+
:- encoding(utf8).
|
|
13
|
+
|
|
11
14
|
:- use_module(library(http/json)).
|
|
12
15
|
:- use_module(library(crypto)).
|
|
13
16
|
:- use_module(library(date)).
|
|
@@ -237,7 +240,47 @@ inventory_entry_field(List, Key, Value) :-
|
|
|
237
240
|
is_list(List),
|
|
238
241
|
memberchk(Key=Value, List).
|
|
239
242
|
|
|
243
|
+
% Pure-function memos, content-addressed by variant_sha1/2, so they need no
|
|
244
|
+
% invalidation: equal inputs always produce equal outputs. Proof evaluation
|
|
245
|
+
% re-parses the same receipt histories and re-validates the same receipts on
|
|
246
|
+
% every coverage run; both are bounded so a long-lived engine cannot grow them
|
|
247
|
+
% without limit.
|
|
248
|
+
:- dynamic pure_memo/3.
|
|
249
|
+
:- dynamic pure_memo_size/1.
|
|
250
|
+
|
|
251
|
+
pure_memo_limit(4096).
|
|
252
|
+
|
|
253
|
+
pure_memo(Kind, Input, Goal, Output) :-
|
|
254
|
+
variant_sha1(Kind-Input, Key),
|
|
255
|
+
( pure_memo(Key, Kind, Cached)
|
|
256
|
+
-> Output = Cached
|
|
257
|
+
; call(Goal, Input, Result),
|
|
258
|
+
pure_memo_remember(Key, Kind, Result),
|
|
259
|
+
Output = Result
|
|
260
|
+
).
|
|
261
|
+
|
|
262
|
+
pure_memo_remember(Key, Kind, Output) :-
|
|
263
|
+
( pure_memo_size(Size)
|
|
264
|
+
-> true
|
|
265
|
+
; Size = 0
|
|
266
|
+
),
|
|
267
|
+
pure_memo_limit(Limit),
|
|
268
|
+
( Size >= Limit
|
|
269
|
+
-> retractall(pure_memo(_, _, _)),
|
|
270
|
+
NextSize = 1
|
|
271
|
+
; NextSize is Size + 1
|
|
272
|
+
),
|
|
273
|
+
retractall(pure_memo_size(_)),
|
|
274
|
+
assertz(pure_memo_size(NextSize)),
|
|
275
|
+
assertz(pure_memo(Key, Kind, Output)).
|
|
276
|
+
|
|
240
277
|
inventory_entries(Raw, Entries) :-
|
|
278
|
+
( (atom(Raw) ; string(Raw))
|
|
279
|
+
-> pure_memo(inventory_entries, Raw, parse_inventory_entries, Entries)
|
|
280
|
+
; parse_inventory_entries(Raw, Entries)
|
|
281
|
+
).
|
|
282
|
+
|
|
283
|
+
parse_inventory_entries(Raw, Entries) :-
|
|
241
284
|
( is_list(Raw)
|
|
242
285
|
-> Entries = Raw
|
|
243
286
|
; Raw = ^^(Value, _)
|
|
@@ -727,8 +770,8 @@ receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, _Context,
|
|
|
727
770
|
proof_contract_binding(Props, ContractBinding),
|
|
728
771
|
(ContractBinding == invalid
|
|
729
772
|
; Receipts == []
|
|
730
|
-
;
|
|
731
|
-
|
|
773
|
+
; pure_memo(receipt_history_well_formed, TestId-Receipts,
|
|
774
|
+
receipt_history_well_formed, false)),
|
|
732
775
|
!.
|
|
733
776
|
receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Evidence) :-
|
|
734
777
|
receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts),
|
|
@@ -745,6 +788,13 @@ receipt_evidence_state(TestId, Scope, Props, Receipts, _Present, Context, Eviden
|
|
|
745
788
|
)
|
|
746
789
|
).
|
|
747
790
|
|
|
791
|
+
receipt_history_well_formed(TestId-Receipts, WellFormed) :-
|
|
792
|
+
( forall(member(Receipt, Receipts), valid_receipt_shape(TestId, Receipt)),
|
|
793
|
+
chronological_receipt_history(Receipts)
|
|
794
|
+
-> WellFormed = true
|
|
795
|
+
; WellFormed = false
|
|
796
|
+
).
|
|
797
|
+
|
|
748
798
|
proof_receipt_entries(Props, Receipts, Present) :-
|
|
749
799
|
( memberchk(proof_receipts=Raw, Props)
|
|
750
800
|
-> Present = true,
|
|
@@ -889,10 +939,10 @@ receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts) :-
|
|
|
889
939
|
( Context.proofBindingMode == per_contract,
|
|
890
940
|
get_dict(TestId, Context.proofTestBindings, CurrentBinding)
|
|
891
941
|
-> include(receipt_with_binding_hash, Receipts, BoundReceipts),
|
|
892
|
-
include(receipt_for_binding(CurrentBinding), BoundReceipts,
|
|
893
|
-
(
|
|
942
|
+
include(receipt_for_binding(CurrentBinding), BoundReceipts, BindingReceipts),
|
|
943
|
+
( BindingReceipts == []
|
|
894
944
|
-> include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
|
|
895
|
-
;
|
|
945
|
+
; SnapshotReceipts = BindingReceipts
|
|
896
946
|
)
|
|
897
947
|
; include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
|
|
898
948
|
).
|
package/src/rule_registry.pl
CHANGED
|
@@ -34,6 +34,13 @@ known_rule('predicate-verifiability').
|
|
|
34
34
|
known_rule('strict-readiness').
|
|
35
35
|
known_rule('semantic-completeness').
|
|
36
36
|
known_rule('proof-contract-symbols').
|
|
37
|
+
known_rule('entity-id-style').
|
|
38
|
+
known_rule('domain-redundancy').
|
|
39
|
+
known_rule('domain-implication').
|
|
40
|
+
known_rule('subject-key-identity').
|
|
41
|
+
known_rule('subject-key-shape').
|
|
42
|
+
known_rule('ontology-quality').
|
|
43
|
+
known_rule('predicate-schema-conformance').
|
|
37
44
|
|
|
38
45
|
rule_enforcement_class('must-priority-coverage', canonical).
|
|
39
46
|
rule_enforcement_class('symbol-coverage', canonical).
|
|
@@ -55,6 +62,13 @@ rule_enforcement_class('predicate-verifiability', advisory).
|
|
|
55
62
|
rule_enforcement_class('strict-readiness', migration).
|
|
56
63
|
rule_enforcement_class('semantic-completeness', migration).
|
|
57
64
|
rule_enforcement_class('proof-contract-symbols', advisory).
|
|
65
|
+
rule_enforcement_class('entity-id-style', advisory).
|
|
66
|
+
rule_enforcement_class('domain-redundancy', advisory).
|
|
67
|
+
rule_enforcement_class('domain-implication', advisory).
|
|
68
|
+
rule_enforcement_class('subject-key-identity', advisory).
|
|
69
|
+
rule_enforcement_class('subject-key-shape', advisory).
|
|
70
|
+
rule_enforcement_class('ontology-quality', advisory).
|
|
71
|
+
rule_enforcement_class('predicate-schema-conformance', advisory).
|
|
58
72
|
|
|
59
73
|
rule_implementation('must-priority-coverage', prolog).
|
|
60
74
|
rule_implementation('symbol-coverage', prolog).
|
|
@@ -76,6 +90,13 @@ rule_implementation('predicate-verifiability', prolog).
|
|
|
76
90
|
rule_implementation('strict-readiness', prolog).
|
|
77
91
|
rule_implementation('semantic-completeness', prolog).
|
|
78
92
|
rule_implementation('proof-contract-symbols', prolog).
|
|
93
|
+
rule_implementation('entity-id-style', prolog).
|
|
94
|
+
rule_implementation('domain-redundancy', prolog).
|
|
95
|
+
rule_implementation('domain-implication', prolog).
|
|
96
|
+
rule_implementation('subject-key-identity', prolog).
|
|
97
|
+
rule_implementation('subject-key-shape', prolog).
|
|
98
|
+
rule_implementation('ontology-quality', prolog).
|
|
99
|
+
rule_implementation('predicate-schema-conformance', typescript).
|
|
79
100
|
|
|
80
101
|
rule_predicate('must-priority-coverage', check_must_priority_coverage).
|
|
81
102
|
rule_predicate('symbol-coverage', check_symbol_coverage).
|
|
@@ -95,6 +116,12 @@ rule_predicate('predicate-verifiability', check_predicate_verifiability).
|
|
|
95
116
|
rule_predicate('strict-readiness', check_strict_readiness).
|
|
96
117
|
rule_predicate('semantic-completeness', check_semantic_completeness).
|
|
97
118
|
rule_predicate('proof-contract-symbols', check_proof_contract_symbols).
|
|
119
|
+
rule_predicate('entity-id-style', check_entity_id_style).
|
|
120
|
+
rule_predicate('domain-redundancy', check_domain_redundancy).
|
|
121
|
+
rule_predicate('domain-implication', check_domain_implication).
|
|
122
|
+
rule_predicate('subject-key-identity', check_subject_key_identity).
|
|
123
|
+
rule_predicate('subject-key-shape', check_subject_key_shape).
|
|
124
|
+
rule_predicate('ontology-quality', check_ontology_quality).
|
|
98
125
|
|
|
99
126
|
rule_predicate_arity('must-priority-coverage', 1).
|
|
100
127
|
rule_predicate_arity('symbol-coverage', 1).
|
|
@@ -114,6 +141,12 @@ rule_predicate_arity('predicate-verifiability', 1).
|
|
|
114
141
|
rule_predicate_arity('strict-readiness', 1).
|
|
115
142
|
rule_predicate_arity('semantic-completeness', 1).
|
|
116
143
|
rule_predicate_arity('proof-contract-symbols', 1).
|
|
144
|
+
rule_predicate_arity('entity-id-style', 1).
|
|
145
|
+
rule_predicate_arity('domain-redundancy', 1).
|
|
146
|
+
rule_predicate_arity('domain-implication', 1).
|
|
147
|
+
rule_predicate_arity('subject-key-identity', 1).
|
|
148
|
+
rule_predicate_arity('subject-key-shape', 1).
|
|
149
|
+
rule_predicate_arity('ontology-quality', 1).
|
|
117
150
|
|
|
118
151
|
rule_description('must-priority-coverage', 'Every must-priority requirement must have a scenario and a test').
|
|
119
152
|
rule_description('symbol-coverage', 'Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path').
|
|
@@ -135,3 +168,10 @@ rule_description('predicate-verifiability', 'Detect requires_predicate links tha
|
|
|
135
168
|
rule_description('strict-readiness', 'Report strict contradiction-readiness levels for requirements that are still prose-only or otherwise not contradiction-ready').
|
|
136
169
|
rule_description('semantic-completeness', 'Every inventoried assertive proposition is modeled or explicitly classified').
|
|
137
170
|
rule_description('proof-contract-symbols', 'Detect unresolved required_proofs.symbol_id values, type-shape required proofs, and proof_bindings.source_file disagreement with the named symbol sourceFile').
|
|
171
|
+
rule_description('entity-id-style', 'Entity IDs name the governed behavior and match their filename stem; new purely numeric IDs are reported at creation boundaries').
|
|
172
|
+
rule_description('domain-redundancy', 'Two current requirements must not ground the identical logical term or share a ground fact unless linked by supersedes or restates').
|
|
173
|
+
rule_description('domain-implication', 'Informational: one requirement\'s numeric bound strictly implies another requirement\'s bound on the same subject and property').
|
|
174
|
+
rule_description('subject-key-identity', 'Subject keys must name a shared component, not be derived from a requirement ID').
|
|
175
|
+
rule_description('subject-key-shape', 'Subject keys follow dotted component.aspect[.sub] with lowercase snake segments').
|
|
176
|
+
rule_description('ontology-quality', 'Informational: predicate schemas whose argument values mostly occur in only one fact are carrying prose instead of a shared vocabulary').
|
|
177
|
+
rule_description('predicate-schema-conformance', 'Predicate facts match a predicate_schema (project-local, or the built-in catalog in the default namespace) and use its declared argument constants').
|