kibi-core 0.13.0 → 0.14.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 +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 +322 -13
- package/src/requirement_proof.pl +52 -2
- package/src/rule_registry.pl +40 -0
- package/src/semantic_quality.pl +750 -0
- package/src/status.pl +41 -15
- 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,
|
|
@@ -324,6 +353,9 @@ kb_detach :-
|
|
|
324
353
|
retractall(kb_dirty),
|
|
325
354
|
retractall(kb_index_ready),
|
|
326
355
|
retractall(kb_index_dirty),
|
|
356
|
+
retractall(kb_index_verified_key(_)),
|
|
357
|
+
retractall(kb_entity_props_memo(_, _)),
|
|
358
|
+
retractall(kb_entity_props_memo_key(_)),
|
|
327
359
|
retractall(kb_index_entity(_, _)),
|
|
328
360
|
retractall(kb_index_type(_, _)),
|
|
329
361
|
retractall(kb_index_tag(_, _)),
|
|
@@ -1089,6 +1121,62 @@ kb_maybe_check_req_contradiction(req, Id, false) :-
|
|
|
1089
1121
|
check_req_contradiction(Id).
|
|
1090
1122
|
kb_maybe_check_req_contradiction(_, _, _).
|
|
1091
1123
|
|
|
1124
|
+
% Stage one upsert inside an already-open transaction. The caller saves once.
|
|
1125
|
+
kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
1126
|
+
memberchk(id=Id, Props),
|
|
1127
|
+
memberchk(SkipContradiction, [true, false]),
|
|
1128
|
+
upsert_change_kind(Id, ChangeKind),
|
|
1129
|
+
kb_assert_entity_no_audit(Type, Props),
|
|
1130
|
+
kb_commit_relationships_no_audit(Relationships),
|
|
1131
|
+
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
1132
|
+
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1133
|
+
kb_commit_relationship_audits(Relationships).
|
|
1134
|
+
|
|
1135
|
+
kb_stage_upserts([], []).
|
|
1136
|
+
kb_stage_upserts([
|
|
1137
|
+
upsert(Type, Props, Relationships, SkipContradiction)|Rest
|
|
1138
|
+
], [ChangeKind|Kinds]) :-
|
|
1139
|
+
kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind),
|
|
1140
|
+
kb_stage_upserts(Rest, Kinds).
|
|
1141
|
+
|
|
1142
|
+
%% kb_commit_upsert_batch(+Entries, -ChangeKinds)
|
|
1143
|
+
% implements REQ-core-atomic-upsert-persistence
|
|
1144
|
+
% Commit many upserts in one RDF transaction and one journal flush.
|
|
1145
|
+
% Entries are upsert(Type, Props, Relationships, SkipContradiction) terms.
|
|
1146
|
+
kb_commit_upsert_batch(Entries, ChangeKinds) :-
|
|
1147
|
+
kb_storage_mode(journaled),
|
|
1148
|
+
!,
|
|
1149
|
+
with_kb_mutex((
|
|
1150
|
+
( kb_dirty -> WasDirty = true ; WasDirty = false ),
|
|
1151
|
+
catch(
|
|
1152
|
+
rdf_transaction((
|
|
1153
|
+
kb_stage(rdf_mutation),
|
|
1154
|
+
kb_stage_upserts(Entries, ChangeKinds)
|
|
1155
|
+
)),
|
|
1156
|
+
Error,
|
|
1157
|
+
( (WasDirty == false -> retractall(kb_dirty) ; true),
|
|
1158
|
+
throw(Error)
|
|
1159
|
+
)
|
|
1160
|
+
),
|
|
1161
|
+
kb_mark_dirty,
|
|
1162
|
+
kb_save_journaled
|
|
1163
|
+
)).
|
|
1164
|
+
kb_commit_upsert_batch(Entries, ChangeKinds) :-
|
|
1165
|
+
kb_attached(Directory),
|
|
1166
|
+
atom_concat(Directory, '/kb.rdf', DataFile),
|
|
1167
|
+
kb_stage(runtime),
|
|
1168
|
+
kb_runtime_diagnostic,
|
|
1169
|
+
kb_stage(lock),
|
|
1170
|
+
with_kb_file_lock(Directory, (
|
|
1171
|
+
audit_store_writable,
|
|
1172
|
+
ensure_snapshot_current(DataFile),
|
|
1173
|
+
rdf_transaction((
|
|
1174
|
+
kb_stage(rdf_mutation),
|
|
1175
|
+
kb_stage_upserts(Entries, ChangeKinds),
|
|
1176
|
+
kb_save_locked(Directory)
|
|
1177
|
+
))
|
|
1178
|
+
)).
|
|
1179
|
+
|
|
1092
1180
|
kb_commit_relationship_audits([]).
|
|
1093
1181
|
kb_commit_relationship_audits([
|
|
1094
1182
|
rel(RelType, FromId, ToId, _Metadata)|Rest
|
|
@@ -1324,6 +1412,42 @@ kb_entity(Id, Type, Props) :-
|
|
|
1324
1412
|
% Raw RDF/entity facts are kept separate from the index wrapper. Rebuilding
|
|
1325
1413
|
% an index must never recurse through the indexed query path.
|
|
1326
1414
|
kb_entity_raw(Id, Type, Props) :-
|
|
1415
|
+
( nonvar(Id)
|
|
1416
|
+
-> rdf_entity_solutions(Id, Solutions),
|
|
1417
|
+
member(Type-Props, Solutions)
|
|
1418
|
+
; kb_entity_raw_rdf(Id, Type, Props)
|
|
1419
|
+
).
|
|
1420
|
+
|
|
1421
|
+
% Fallback: read from legacy entity/4 facts loaded from .pl files
|
|
1422
|
+
kb_entity_raw(Id, Type, Props) :-
|
|
1423
|
+
entity(Type, Id, _Title, PropList),
|
|
1424
|
+
convert_legacy_props(PropList, Props).
|
|
1425
|
+
|
|
1426
|
+
%% rdf_entity_solutions(+Id, -Solutions)
|
|
1427
|
+
% Coverage, proof, and check predicates look up the same entities tens of
|
|
1428
|
+
% thousands of times, and every lookup materializes and decodes every
|
|
1429
|
+
% property (including receipt histories). Memoize the decoded Type-Props
|
|
1430
|
+
% solutions per entity for the current graph and RDF generation.
|
|
1431
|
+
rdf_entity_solutions(Id, Solutions) :-
|
|
1432
|
+
( kb_graph(Graph)
|
|
1433
|
+
-> rdf_generation(Generation),
|
|
1434
|
+
Key = Graph-Generation,
|
|
1435
|
+
( kb_entity_props_memo_key(Current),
|
|
1436
|
+
Current == Key
|
|
1437
|
+
-> true
|
|
1438
|
+
; retractall(kb_entity_props_memo(_, _)),
|
|
1439
|
+
retractall(kb_entity_props_memo_key(_)),
|
|
1440
|
+
assertz(kb_entity_props_memo_key(Key))
|
|
1441
|
+
),
|
|
1442
|
+
( kb_entity_props_memo(Id, Cached)
|
|
1443
|
+
-> Solutions = Cached
|
|
1444
|
+
; findall(Type-Props, kb_entity_raw_rdf(Id, Type, Props), Solutions),
|
|
1445
|
+
assertz(kb_entity_props_memo(Id, Solutions))
|
|
1446
|
+
)
|
|
1447
|
+
; Solutions = []
|
|
1448
|
+
).
|
|
1449
|
+
|
|
1450
|
+
kb_entity_raw_rdf(Id, Type, Props) :-
|
|
1327
1451
|
kb_graph(Graph),
|
|
1328
1452
|
% Find entity by pattern - use unquoted namespace term kb:type
|
|
1329
1453
|
( var(Id)
|
|
@@ -1344,20 +1468,37 @@ kb_entity_raw(Id, Type, Props) :-
|
|
|
1344
1468
|
literal_to_value(Key, ValueLiteral, Value)
|
|
1345
1469
|
), Props).
|
|
1346
1470
|
|
|
1347
|
-
%
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1471
|
+
% Verifying the index counts every type triple in the store. Remember the
|
|
1472
|
+
% inputs of the last successful check (graph, RDF generation, legacy fact
|
|
1473
|
+
% count, and the index's own entity count) and skip the recount while all of
|
|
1474
|
+
% them are unchanged: the recount would then return the same answer. The
|
|
1475
|
+
% index count must be part of the key because incremental index updates are
|
|
1476
|
+
% plain Prolog facts that an RDF rollback does not undo, while the RDF
|
|
1477
|
+
% generation does revert.
|
|
1352
1478
|
kb_ensure_indexes :-
|
|
1353
1479
|
( kb_index_ready,
|
|
1354
1480
|
\+ kb_index_dirty,
|
|
1355
1481
|
kb_index_entity_count(Expected),
|
|
1356
|
-
|
|
1482
|
+
index_verification_key(Expected, Key),
|
|
1483
|
+
( kb_index_verified_key(Verified),
|
|
1484
|
+
Verified == Key
|
|
1485
|
+
-> true
|
|
1486
|
+
; current_entity_index_count(Expected),
|
|
1487
|
+
retractall(kb_index_verified_key(_)),
|
|
1488
|
+
assertz(kb_index_verified_key(Key))
|
|
1489
|
+
)
|
|
1357
1490
|
-> true
|
|
1358
1491
|
; kb_rebuild_indexes
|
|
1359
1492
|
).
|
|
1360
1493
|
|
|
1494
|
+
index_verification_key(IndexCount, key(Graph, Generation, LegacyCount, IndexCount)) :-
|
|
1495
|
+
(kb_graph(Graph) -> true ; Graph = none),
|
|
1496
|
+
rdf_generation(Generation),
|
|
1497
|
+
( predicate_property(kb:entity(_, _, _, _), number_of_clauses(Count))
|
|
1498
|
+
-> LegacyCount = Count
|
|
1499
|
+
; LegacyCount = 0
|
|
1500
|
+
).
|
|
1501
|
+
|
|
1361
1502
|
% Rebuild all acceleration structures from RDF/entity facts. This is
|
|
1362
1503
|
% intentionally disposable: no query result is written back to RDF when an
|
|
1363
1504
|
% index is stale or externally invalidated.
|
|
@@ -1380,6 +1521,7 @@ kb_rebuild_indexes :-
|
|
|
1380
1521
|
assertz(kb_index_triple_count(TripleCount)),
|
|
1381
1522
|
retractall(kb_index_dirty),
|
|
1382
1523
|
retractall(kb_index_ready),
|
|
1524
|
+
retractall(kb_index_verified_key(_)),
|
|
1383
1525
|
assertz(kb_index_ready)
|
|
1384
1526
|
)).
|
|
1385
1527
|
|
|
@@ -1515,6 +1657,97 @@ kb_query_entities(TypeFilter, IdFilter, Tags, SourceFilter, Limit, Offset, Rows,
|
|
|
1515
1657
|
kb_entity(Id, Type, Props) ),
|
|
1516
1658
|
Rows).
|
|
1517
1659
|
|
|
1660
|
+
%% kb_query_proof_contracts(+Id, +Limit, +Offset, -Rows)
|
|
1661
|
+
% Page proof-campaign candidates while reading only contract and binding RDF
|
|
1662
|
+
% properties. In particular, do not call kb_entity/3 here: that materializes
|
|
1663
|
+
% complete entity properties, including potentially large receipt histories.
|
|
1664
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
1665
|
+
kb_query_proof_contracts(IdFilter, Limit, Offset, Rows) :-
|
|
1666
|
+
integer(Limit),
|
|
1667
|
+
integer(Offset),
|
|
1668
|
+
Limit >= 0,
|
|
1669
|
+
Offset >= 0,
|
|
1670
|
+
findall([Id, test, Projected],
|
|
1671
|
+
( limit(Limit,
|
|
1672
|
+
offset(Offset,
|
|
1673
|
+
order_by([asc(Id)],
|
|
1674
|
+
distinct(proof_contract_candidate(IdFilter, Id))))),
|
|
1675
|
+
proof_contract_properties(Id, Contract, Bindings, Source),
|
|
1676
|
+
optional_projected_property(proof_bindings, Bindings,
|
|
1677
|
+
BindingProps),
|
|
1678
|
+
optional_projected_property(source, Source, SourceProps),
|
|
1679
|
+
append([[id=Id, proof_contract=Contract],
|
|
1680
|
+
BindingProps, SourceProps],
|
|
1681
|
+
Projected) ),
|
|
1682
|
+
Rows).
|
|
1683
|
+
|
|
1684
|
+
optional_projected_property(_Key, Value, []) :-
|
|
1685
|
+
var(Value),
|
|
1686
|
+
!.
|
|
1687
|
+
optional_projected_property(Key, Value, [Key=Value]).
|
|
1688
|
+
|
|
1689
|
+
proof_contract_properties(Id, Contract, Bindings) :-
|
|
1690
|
+
proof_contract_properties(Id, Contract, Bindings, _Source).
|
|
1691
|
+
|
|
1692
|
+
% Source is projected alongside the contract so per-contract receipt binding
|
|
1693
|
+
% can hash the authored document without materializing receipt histories.
|
|
1694
|
+
proof_contract_properties(Id, Contract, Bindings, Source) :-
|
|
1695
|
+
( ( kb_graph(Graph),
|
|
1696
|
+
entity_id_to_uri(Id, EntityURI),
|
|
1697
|
+
rdf(EntityURI, kb:type, TypeLiteral, Graph),
|
|
1698
|
+
literal_to_atom(TypeLiteral, test),
|
|
1699
|
+
kb_property_uri(proof_contract, ContractURI),
|
|
1700
|
+
rdf(EntityURI, ContractURI, ContractLiteral, Graph)
|
|
1701
|
+
)
|
|
1702
|
+
-> literal_to_value(proof_contract, ContractLiteral, Contract),
|
|
1703
|
+
( kb_property_uri(proof_bindings, BindingsURI),
|
|
1704
|
+
rdf(EntityURI, BindingsURI, BindingsLiteral, Graph)
|
|
1705
|
+
-> literal_to_value(proof_bindings, BindingsLiteral, Bindings)
|
|
1706
|
+
; true
|
|
1707
|
+
),
|
|
1708
|
+
( kb_property_uri(source, SourceURI),
|
|
1709
|
+
rdf(EntityURI, SourceURI, SourceLiteral, Graph)
|
|
1710
|
+
-> literal_to_value(source, SourceLiteral, Source)
|
|
1711
|
+
; true
|
|
1712
|
+
)
|
|
1713
|
+
; entity(test, Id, _, Properties),
|
|
1714
|
+
memberchk(proof_contract=Contract, Properties),
|
|
1715
|
+
( memberchk(proof_bindings=Bindings, Properties)
|
|
1716
|
+
-> true
|
|
1717
|
+
; true
|
|
1718
|
+
),
|
|
1719
|
+
( memberchk(source=Source, Properties)
|
|
1720
|
+
-> true
|
|
1721
|
+
; true
|
|
1722
|
+
)
|
|
1723
|
+
).
|
|
1724
|
+
|
|
1725
|
+
%% kb_property_uri(+Key, -URI)
|
|
1726
|
+
% Property predicates are asserted in-session as the prefixed atom 'kb:Key',
|
|
1727
|
+
% but a store reloaded from its snapshot carries the expanded 'urn-kibi:Key'
|
|
1728
|
+
% form. Direct RDF lookups must accept both (as uri_to_key/2 already does),
|
|
1729
|
+
% or projections silently miss every entity of a reloaded store.
|
|
1730
|
+
kb_property_uri(Key, URI) :-
|
|
1731
|
+
kb_uri(BaseURI),
|
|
1732
|
+
atom_concat(BaseURI, Key, URI).
|
|
1733
|
+
kb_property_uri(Key, URI) :-
|
|
1734
|
+
atom_concat('kb:', Key, URI).
|
|
1735
|
+
|
|
1736
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
1737
|
+
proof_contract_candidate(none, Id) :-
|
|
1738
|
+
kb_graph(Graph),
|
|
1739
|
+
kb_property_uri(proof_contract, ContractURI),
|
|
1740
|
+
rdf(EntityURI, ContractURI, _, Graph),
|
|
1741
|
+
entity_uri_to_id(EntityURI, Id),
|
|
1742
|
+
rdf(EntityURI, kb:type, TypeLiteral, Graph),
|
|
1743
|
+
literal_to_atom(TypeLiteral, test).
|
|
1744
|
+
proof_contract_candidate(none, Id) :-
|
|
1745
|
+
entity(test, Id, _, Properties),
|
|
1746
|
+
memberchk(proof_contract=_, Properties).
|
|
1747
|
+
proof_contract_candidate(some(Id), Id) :-
|
|
1748
|
+
atom(Id),
|
|
1749
|
+
proof_contract_properties(Id, _, _).
|
|
1750
|
+
|
|
1518
1751
|
indexed_entity_match(TypeFilter, IdFilter, Tags, SourceFilter, Id) :-
|
|
1519
1752
|
( IdFilter == none
|
|
1520
1753
|
-> kb_index_entity(Id, _)
|
|
@@ -1573,10 +1806,74 @@ kb_search_entities(TypeFilter, Query, Limit, Offset, Rows, Count) :-
|
|
|
1573
1806
|
length(Ids, Count),
|
|
1574
1807
|
drop_index_ids(Offset, Ids, Remaining),
|
|
1575
1808
|
take_index_ids(Limit, Remaining, PageIds),
|
|
1576
|
-
|
|
1577
|
-
|
|
1809
|
+
search_candidate_rows(PageIds, Rows).
|
|
1810
|
+
|
|
1811
|
+
%% kb_list_search_candidates(+Type, +Limit, +Offset, -Rows, -Count)
|
|
1812
|
+
% Page every indexed entity (optionally of one type) as projected search
|
|
1813
|
+
% candidate rows, ordered by id. Semantic discovery uses it when no lexical
|
|
1814
|
+
% token can pre-filter the candidate set.
|
|
1815
|
+
kb_list_search_candidates(TypeFilter, Limit, Offset, Rows, Count) :-
|
|
1816
|
+
integer(Limit),
|
|
1817
|
+
integer(Offset),
|
|
1818
|
+
Limit >= 0,
|
|
1819
|
+
Offset >= 0,
|
|
1820
|
+
kb_ensure_indexes,
|
|
1821
|
+
findall(Id,
|
|
1822
|
+
( kb_index_entity(Id, _),
|
|
1823
|
+
(TypeFilter == none -> true ; kb_index_type(TypeFilter, Id)) ),
|
|
1824
|
+
RawIds),
|
|
1825
|
+
sort(RawIds, Ids),
|
|
1826
|
+
length(Ids, Count),
|
|
1827
|
+
drop_index_ids(Offset, Ids, Remaining),
|
|
1828
|
+
take_index_ids(Limit, Remaining, PageIds),
|
|
1829
|
+
search_candidate_rows(PageIds, Rows).
|
|
1830
|
+
|
|
1831
|
+
%% kb_entity_ids(-Ids)
|
|
1832
|
+
% Every entity id, without materializing any properties.
|
|
1833
|
+
kb_entity_ids(Ids) :-
|
|
1834
|
+
kb_ensure_indexes,
|
|
1835
|
+
findall(Id, kb_index_entity(Id, _), RawIds),
|
|
1836
|
+
sort(RawIds, Ids).
|
|
1837
|
+
|
|
1838
|
+
% Search candidates carry only the properties ranking, snippets, and summary
|
|
1839
|
+
% output read. Large structured properties (receipt histories, proof
|
|
1840
|
+
% contracts, semantic inventories, rule IR) stay in the store: a candidate
|
|
1841
|
+
% page of receipt-bearing tests otherwise exceeds the bounded engine output.
|
|
1842
|
+
% Callers that need complete entities reload the final page by id.
|
|
1843
|
+
search_candidate_rows(PageIds, Rows) :-
|
|
1844
|
+
findall([Id, Type, Projected],
|
|
1845
|
+
( member(Id, PageIds),
|
|
1846
|
+
kb_entity(Id, Type, Props),
|
|
1847
|
+
include(search_candidate_property, Props, Projected) ),
|
|
1578
1848
|
Rows).
|
|
1579
1849
|
|
|
1850
|
+
search_candidate_property(Key=_) :-
|
|
1851
|
+
search_candidate_key(Key).
|
|
1852
|
+
|
|
1853
|
+
search_candidate_key(id).
|
|
1854
|
+
search_candidate_key(title).
|
|
1855
|
+
search_candidate_key(status).
|
|
1856
|
+
search_candidate_key(priority).
|
|
1857
|
+
search_candidate_key(severity).
|
|
1858
|
+
search_candidate_key(owner).
|
|
1859
|
+
search_candidate_key(tags).
|
|
1860
|
+
search_candidate_key(source).
|
|
1861
|
+
search_candidate_key(sourceFile).
|
|
1862
|
+
search_candidate_key(sourceLine).
|
|
1863
|
+
search_candidate_key(sourceColumn).
|
|
1864
|
+
search_candidate_key(sourceEndLine).
|
|
1865
|
+
search_candidate_key(sourceEndColumn).
|
|
1866
|
+
search_candidate_key(source_line).
|
|
1867
|
+
search_candidate_key(source_end_line).
|
|
1868
|
+
search_candidate_key(updated_at).
|
|
1869
|
+
search_candidate_key(created_at).
|
|
1870
|
+
search_candidate_key(semantic_text).
|
|
1871
|
+
search_candidate_key(text_ref).
|
|
1872
|
+
search_candidate_key(body).
|
|
1873
|
+
search_candidate_key(content).
|
|
1874
|
+
search_candidate_key(markdownBody).
|
|
1875
|
+
search_candidate_key(markdown_body).
|
|
1876
|
+
|
|
1580
1877
|
indexed_token_match(QueryToken, Id) :-
|
|
1581
1878
|
kb_index_token(IndexedToken, Id),
|
|
1582
1879
|
sub_atom(IndexedToken, _, _, _, QueryToken),
|
|
@@ -2269,10 +2566,14 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
|
2269
2566
|
FactA \= FactB,
|
|
2270
2567
|
scope_intersects(ScopeA, ScopeB),
|
|
2271
2568
|
intervals_overlap(ValidFromA, ValidToA, ValidFromB, ValidToB),
|
|
2272
|
-
(
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2569
|
+
% Compare canonical quantities (30 min == 1800 s); witnesses keep the
|
|
2570
|
+
% authored values so evidence still points at the source text.
|
|
2571
|
+
canonical_quantity(ValTypeA, ValA, UnitA, CanonTypeA, CanonValA, CanonUnitA),
|
|
2572
|
+
canonical_quantity(ValTypeB, ValB, UnitB, CanonTypeB, CanonValB, CanonUnitB),
|
|
2573
|
+
( polarity_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, ScopeA, PolarityA,
|
|
2574
|
+
OpB, CanonTypeB, CanonValB, CanonUnitB, ScopeB, PolarityB, Reason)
|
|
2575
|
+
; property_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, PolarityA,
|
|
2576
|
+
OpB, CanonTypeB, CanonValB, CanonUnitB, PolarityB, Reason)
|
|
2276
2577
|
),
|
|
2277
2578
|
property_conflict_side(ReqA, FactA, SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, ScopeA, PolarityA, ValidFromA, ValidToA, Left),
|
|
2278
2579
|
property_conflict_side(ReqB, FactB, SubjectKey, PropertyKey, OpB, ValTypeB, ValB, UnitB, ScopeB, PolarityB, ValidFromB, ValidToB, Right),
|
|
@@ -2418,6 +2719,14 @@ fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope,
|
|
|
2418
2719
|
( memberchk(scope=ScopeRaw, Props) -> normalize_term_atom(ScopeRaw, Scope) ; Scope = '' ),
|
|
2419
2720
|
( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = require ).
|
|
2420
2721
|
|
|
2722
|
+
%% canonical_property_tuple(+FactId, -Subject, -Property, -Op, -ValType, -Value, -Unit, -Scope, -Polarity)
|
|
2723
|
+
% fact_property_tuple/9 with the quantity converted to its canonical base unit
|
|
2724
|
+
% (units.pl). Used only for comparison; the authored fact is never rewritten.
|
|
2725
|
+
% implements REQ-kibi-unit-canonicalization
|
|
2726
|
+
canonical_property_tuple(FactId, Subject, Property, Op, CanonType, CanonValue, CanonUnit, Scope, Polarity) :-
|
|
2727
|
+
fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope, Polarity),
|
|
2728
|
+
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit).
|
|
2729
|
+
|
|
2421
2730
|
%% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
|
|
2422
2731
|
% Read one project-local ontology predicate schema fact.
|
|
2423
2732
|
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,
|
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').
|