kibi-core 0.6.1 → 0.6.3

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.
Files changed (3) hide show
  1. package/package.json +5 -2
  2. package/src/checks.pl +2 -2
  3. package/src/kb.pl +89 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -9,7 +9,10 @@
9
9
  "bun": ">=1.0"
10
10
  },
11
11
  "main": "./src/kb.pl",
12
- "files": ["src/**/*.pl", "schema/**/*.pl"],
12
+ "files": [
13
+ "src/**/*.pl",
14
+ "schema/**/*.pl"
15
+ ],
13
16
  "license": "AGPL-3.0-or-later",
14
17
  "author": "Piotr Franczyk",
15
18
  "repository": {
package/src/checks.pl CHANGED
@@ -107,7 +107,7 @@ symbol_coverage_violation(SymbolId, violation(
107
107
  'symbol-coverage',
108
108
  SymbolId,
109
109
  "Production symbol lacks qualifying requirement coverage.",
110
- "Add 'covered_by: TEST-xxx' for production coverage, and ensure that test reaches the requirement through a scenario->test path or direct req->test fallback when no scenario exists.",
110
+ "Add 'covered_by: TEST-xxx' for production coverage. If the requirement has specified_by a scenario, use verified_by(scenario,test) or validates(test,scenario). Direct verified_by(req,test) does not count when a scenario exists.",
111
111
  Source
112
112
  )) :-
113
113
  violation_source(SymbolId, symbol, Source).
@@ -718,7 +718,7 @@ strict_readiness_issue(
718
718
  strict_readiness_issue(
719
719
  ReqId,
720
720
  "Strict readiness: not-ready (strict-ready). Requirement has strict subject and property facts but no contradiction-ready matched pair, so contradiction checks still skip it.",
721
- "Align constrains and requires_property on the same subject_key, and keep the requirement current if it should participate in contradiction checks"
721
+ "Ensure constrains and requires_property use the same subject_key, and keep the requirement current if it should participate in contradiction checks"
722
722
  ) :-
723
723
  kb_entity(ReqId, req, _),
724
724
  strict_readiness_level(ReqId, strict_ready).
package/src/kb.pl CHANGED
@@ -10,6 +10,7 @@
10
10
  kb_log_entity_upsert/3,
11
11
  kb_retract_entity/1,
12
12
  kb_retract_entity/3,
13
+ kb_retract_entity_relationships/1,
13
14
  kb_entity/3,
14
15
  kb_entities_by_source/2,
15
16
  kb_assert_relationship/4,
@@ -279,9 +280,19 @@ kb_assert_entity_no_audit(Type, Props) :-
279
280
  % Execute RDF operations with mutex protection
280
281
  with_kb_mutex((
281
282
  % Create entity URI using prefix notation for namespace expansion
282
- format(atom(EntityURI), 'kb:entity/~w', [Id]),
283
- % Upsert semantics: remove any existing triples for this entity first.
284
- rdf_retractall(EntityURI, _, _, Graph),
283
+ entity_id_to_uri(Id, EntityURI),
284
+ % Upsert semantics: remove only property triples, preserving relationships.
285
+ % Relationship triples have entity URI objects (kb:entity/...);
286
+ % property triples have typed literal objects (_^^xsd:...).
287
+ forall(
288
+ ( rdf(EntityURI, Prop, Obj, Graph),
289
+ ( atom(Obj)
290
+ -> \+ entity_uri_to_id(Obj, _)
291
+ ; true
292
+ )
293
+ ),
294
+ rdf_retractall(EntityURI, Prop, Obj, Graph)
295
+ ),
285
296
  % Store type as string literal to prevent URI interpretation
286
297
  atom_string(Type, TypeStr),
287
298
  rdf_assert(EntityURI, kb:type, TypeStr^^'http://www.w3.org/2001/XMLSchema#string', Graph),
@@ -292,6 +303,22 @@ kb_assert_entity_no_audit(Type, Props) :-
292
303
  )
293
304
  )).
294
305
 
306
+ %% kb_retract_entity_relationships(+Id)
307
+ % Remove all relationship triples for an entity, preserving property triples.
308
+ % Used by projectStagedEntities to clear stale relationships before re-asserting.
309
+ kb_retract_entity_relationships(Id) :-
310
+ kb_graph(Graph),
311
+ with_kb_mutex((
312
+ entity_id_to_uri(Id, EntityURI),
313
+ forall(
314
+ ( rdf(EntityURI, RelURI, TargetURI, Graph),
315
+ atom(TargetURI),
316
+ entity_uri_to_id(TargetURI, _)
317
+ ),
318
+ rdf_retractall(EntityURI, RelURI, TargetURI, Graph)
319
+ )
320
+ )).
321
+
295
322
  %% kb_log_entity_upsert(+ChangeKind, +Type, +Properties)
296
323
  % Append the audit entry for a successfully committed entity upsert.
297
324
  kb_log_entity_upsert(ChangeKind, Type, Props) :-
@@ -319,7 +346,7 @@ kb_retract_entity(Id, Type, AuditProps) :-
319
346
  kb_graph(Graph),
320
347
  with_kb_mutex((
321
348
  % Create entity URI
322
- atom_concat('kb:entity/', Id, EntityURI),
349
+ entity_id_to_uri(Id, EntityURI),
323
350
  % Remove all triples for this entity
324
351
  rdf_retractall(EntityURI, _, _, Graph),
325
352
  % Log to audit
@@ -357,8 +384,8 @@ kb_entity(Id, Type, Props) :-
357
384
  % Find entity by pattern - use unquoted namespace term kb:type
358
385
  ( var(Id)
359
386
  -> rdf(EntityURI, kb:type, TypeLiteral, Graph),
360
- atom_concat('kb:entity/', Id, EntityURI)
361
- ; atom_concat('kb:entity/', Id, EntityURI),
387
+ entity_uri_to_id(EntityURI, Id)
388
+ ; entity_id_to_uri(Id, EntityURI),
362
389
  rdf(EntityURI, kb:type, TypeLiteral, Graph)
363
390
  ),
364
391
  % Extract type - convert string literal to atom
@@ -430,12 +457,24 @@ kb_assert_relationship(RelType, FromId, ToId, Metadata) :-
430
457
  % leave partial audit residue.
431
458
  kb_assert_relationship_no_audit(RelType, FromId, ToId, _Metadata) :-
432
459
  kb_graph(Graph),
433
- % Validate entities exist and relationship is valid
434
- % Use once/1 to keep this predicate deterministic even if the store
435
- % contains duplicate type triples from previous versions.
436
- once(kb_entity(FromId, FromType, _)),
437
- once(kb_entity(ToId, ToType, _)),
438
- validate_relationship(RelType, FromType, ToType),
460
+ % Validate source entity exists
461
+ ( once(kb_entity(FromId, FromType, _))
462
+ -> true
463
+ ; throw(error(existence_error(entity, FromId),
464
+ context(kb_assert_relationship, 'Source entity does not exist')))
465
+ ),
466
+ % Validate target entity exists
467
+ ( once(kb_entity(ToId, ToType, _))
468
+ -> true
469
+ ; throw(error(existence_error(entity, ToId),
470
+ context(kb_assert_relationship, 'Target entity does not exist')))
471
+ ),
472
+ % Validate relationship type and direction
473
+ ( validate_relationship(RelType, FromType, ToType)
474
+ -> true
475
+ ; throw(error(type_error(relationship, RelType),
476
+ context(kb_assert_relationship, 'Invalid relationship: ~w from ~w to ~w'-[RelType, FromType, ToType])))
477
+ ),
439
478
  validate_symbol_role_compatibility(RelType, FromId, ToId),
440
479
  % NOTE: Strict-lane fact_kind pairing is validated at the MCP layer
441
480
  % via validateStrictLanePairing() before the transaction begins.
@@ -444,8 +483,8 @@ kb_assert_relationship_no_audit(RelType, FromId, ToId, _Metadata) :-
444
483
  % Execute RDF operations with mutex protection
445
484
  with_kb_mutex((
446
485
  % Create entity URIs
447
- atom_concat('kb:entity/', FromId, FromURI),
448
- atom_concat('kb:entity/', ToId, ToURI),
486
+ entity_id_to_uri(FromId, FromURI),
487
+ entity_id_to_uri(ToId, ToURI),
449
488
  % Create relationship property URI (full URI to match saved/loaded RDF)
450
489
  kb_uri(BaseURI),
451
490
  atom_concat(BaseURI, RelType, RelURI),
@@ -528,14 +567,45 @@ kb_relationship(RelType, FromId, ToId) :-
528
567
  % Create relationship property URI (full URI to match loaded RDF)
529
568
  kb_uri(BaseURI),
530
569
  atom_concat(BaseURI, RelType, RelURI),
531
- % Find matching relationships
570
+ kb_relationship_with_reluri(RelURI, Graph, FromId, ToId).
571
+
572
+ kb_relationship_with_reluri(RelURI, Graph, FromId, ToId) :-
573
+ nonvar(FromId), nonvar(ToId), !,
574
+ entity_id_to_uri(FromId, FromURI),
575
+ entity_id_to_uri(ToId, ToURI),
576
+ rdf(FromURI, RelURI, ToURI, Graph).
577
+ kb_relationship_with_reluri(RelURI, Graph, FromId, ToId) :-
578
+ nonvar(FromId), var(ToId), !,
579
+ entity_id_to_uri(FromId, FromURI),
532
580
  rdf(FromURI, RelURI, ToURI, Graph),
533
- % Extract IDs from URIs
534
- atom_concat('kb:entity/', FromId, FromURI),
535
- atom_concat('kb:entity/', ToId, ToURI).
581
+ entity_uri_to_id(ToURI, ToId).
582
+ kb_relationship_with_reluri(RelURI, Graph, FromId, ToId) :-
583
+ var(FromId), nonvar(ToId), !,
584
+ entity_id_to_uri(ToId, ToURI),
585
+ rdf(FromURI, RelURI, ToURI, Graph),
586
+ entity_uri_to_id(FromURI, FromId).
587
+ kb_relationship_with_reluri(RelURI, Graph, FromId, ToId) :-
588
+ rdf(FromURI, RelURI, ToURI, Graph),
589
+ entity_uri_to_id(FromURI, FromId),
590
+ entity_uri_to_id(ToURI, ToId).
536
591
 
537
592
  % Helper predicates
538
593
 
594
+ %% entity_id_to_uri(+Id, -URI)
595
+ % Build the canonical entity URI used for RDF read/write.
596
+ entity_id_to_uri(Id, URI) :-
597
+ format(atom(URI), 'kb:entity/~w', [Id]).
598
+
599
+ %% entity_uri_to_id(+URI, -Id)
600
+ % Extract entity ID from canonical or legacy entity URIs.
601
+ entity_uri_to_id(URI, Id) :-
602
+ ( kb_uri(BaseURI),
603
+ atom_concat(BaseURI, 'entity/', Prefix),
604
+ atom_concat(Prefix, Id, URI)
605
+ -> true
606
+ ; atom_concat('kb:entity/', Id, URI)
607
+ ).
608
+
539
609
  %% store_property(+EntityURI, +Key, +Value, +Graph)
540
610
  % Store a property as an RDF triple with appropriate datatype.
541
611
  % All values are stored as typed string literals to avoid URI interpretation issues.
@@ -1212,8 +1282,8 @@ symbol_has_req_coverage(Symbol, Req) :-
1212
1282
  production_symbol_covered_for_requirement(Symbol, Req).
1213
1283
 
1214
1284
  test_covers_requirement(Test, Req) :-
1215
- requirement_test_fallback_allowed(Req),
1216
1285
  requirement_verified_by_test(Req, Test),
1286
+ requirement_test_fallback_allowed(Req),
1217
1287
  test_satisfies_requirement_semantics(Test, Req).
1218
1288
  test_covers_requirement(Test, Req) :-
1219
1289
  kb_relationship(specified_by, Req, Scenario),