kibi-cli 2.21.0 → 2.21.2

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 (105) hide show
  1. package/dist/cli-operation-metadata.d.ts +1 -1
  2. package/dist/cli-operation-metadata.js +1 -1
  3. package/dist/commands/doctor.js +13 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +15 -3
  6. package/dist/commands/legacy-storage-migration.d.ts.map +1 -1
  7. package/dist/commands/legacy-storage-migration.js +26 -1
  8. package/dist/commands/migrate.d.ts.map +1 -1
  9. package/dist/commands/migrate.js +123 -5
  10. package/dist/commands/sync/persistence.d.ts.map +1 -1
  11. package/dist/commands/sync/persistence.js +4 -0
  12. package/dist/decision-firmness.d.ts +39 -0
  13. package/dist/decision-firmness.d.ts.map +1 -0
  14. package/dist/decision-firmness.js +115 -0
  15. package/dist/extractors/markdown.d.ts +2 -0
  16. package/dist/extractors/markdown.d.ts.map +1 -1
  17. package/dist/extractors/markdown.js +14 -0
  18. package/dist/operations/migration/schema6.d.ts +2 -0
  19. package/dist/operations/migration/schema6.d.ts.map +1 -1
  20. package/dist/operations/migration/schema6.js +44 -1
  21. package/dist/operations/migration/supersedes-reason.d.ts +26 -0
  22. package/dist/operations/migration/supersedes-reason.d.ts.map +1 -0
  23. package/dist/operations/migration/supersedes-reason.js +69 -0
  24. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  25. package/dist/operations/modeling/predicate-applyplan.js +10 -2
  26. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  27. package/dist/operations/modeling/predicate-loader.js +11 -0
  28. package/dist/operations/modeling/predicate-types.d.ts +32 -1
  29. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  30. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  31. package/dist/operations/modeling/suggest-predicates.js +175 -35
  32. package/dist/operations/mutation/delete.d.ts +5 -1
  33. package/dist/operations/mutation/delete.d.ts.map +1 -1
  34. package/dist/operations/mutation/delete.js +223 -17
  35. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  36. package/dist/operations/mutation/serialization.js +1 -0
  37. package/dist/operations/mutation/types.d.ts +37 -0
  38. package/dist/operations/mutation/types.d.ts.map +1 -1
  39. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  40. package/dist/operations/mutation/upsert.js +54 -2
  41. package/dist/operations/planning/apply-plan.d.ts +6 -1
  42. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  43. package/dist/operations/planning/apply-plan.js +8 -2
  44. package/dist/public/operations/catalog.d.ts +12 -3
  45. package/dist/public/operations/catalog.d.ts.map +1 -1
  46. package/dist/public/operations/check-executor.d.ts.map +1 -1
  47. package/dist/public/operations/check-executor.js +3 -0
  48. package/dist/public/operations/contracts.d.ts.map +1 -1
  49. package/dist/public/operations/contracts.js +29 -1
  50. package/dist/public/operations/decision-firmness.d.ts +34 -0
  51. package/dist/public/operations/decision-firmness.d.ts.map +1 -0
  52. package/dist/public/operations/decision-firmness.js +216 -0
  53. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  54. package/dist/public/operations/discovery-executors.js +16 -2
  55. package/dist/public/operations/migration-plan.js +1 -1
  56. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  57. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  58. package/dist/public/operations/specs/check-rules.generated.js +5 -1
  59. package/dist/public/operations/specs/check.d.ts +1 -1
  60. package/dist/public/operations/specs/mutation-schemas.d.ts +5 -0
  61. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  62. package/dist/public/operations/specs/mutation-schemas.js +5 -0
  63. package/dist/public/operations/specs/mutation.d.ts +6 -2
  64. package/dist/public/operations/specs/mutation.d.ts.map +1 -1
  65. package/dist/public/operations/specs/mutation.js +6 -2
  66. package/dist/public/operations/specs/upsert.d.ts +5 -0
  67. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  68. package/dist/public/schemas/entity.d.ts.map +1 -1
  69. package/dist/public/schemas/entity.js +11 -0
  70. package/dist/public/skills/kibi-traceability/SKILL.md +6 -4
  71. package/dist/public/skills/kibi-usage/SKILL.md +50 -10
  72. package/dist/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  73. package/dist/relationships/shards.d.ts +10 -0
  74. package/dist/relationships/shards.d.ts.map +1 -1
  75. package/dist/relationships/shards.js +27 -11
  76. package/dist/schemas/entity.schema.json +28 -0
  77. package/dist/search-answer.d.ts +31 -2
  78. package/dist/search-answer.d.ts.map +1 -1
  79. package/dist/search-answer.js +115 -17
  80. package/dist/types/entities.d.ts +4 -0
  81. package/dist/types/entities.d.ts.map +1 -1
  82. package/dist/utils/kb-manifest.d.ts +6 -0
  83. package/dist/utils/kb-manifest.d.ts.map +1 -1
  84. package/dist/utils/kb-manifest.js +6 -0
  85. package/dist/utils/rule-registry.generated.d.ts +21 -0
  86. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  87. package/dist/utils/rule-registry.generated.js +25 -0
  88. package/dist/utils/schema-version.d.ts +1 -1
  89. package/dist/utils/schema-version.d.ts.map +1 -1
  90. package/dist/utils/schema-version.js +3 -1
  91. package/package.json +2 -2
  92. package/schema/entities.pl +6 -0
  93. package/src/public/operations/check-executor.ts +6 -0
  94. package/src/public/operations/contracts.ts +33 -1
  95. package/src/public/operations/decision-firmness.ts +280 -0
  96. package/src/public/operations/discovery-executors.ts +17 -2
  97. package/src/public/operations/migration-plan.ts +1 -1
  98. package/src/public/operations/specs/check-rules.generated.ts +5 -1
  99. package/src/public/operations/specs/mutation-schemas.ts +6 -0
  100. package/src/public/operations/specs/mutation.ts +7 -2
  101. package/src/public/schemas/entity.ts +12 -0
  102. package/src/public/skills/kibi-traceability/SKILL.md +6 -4
  103. package/src/public/skills/kibi-usage/SKILL.md +50 -10
  104. package/src/public/skills/kibi-usage/resources/source-authoring.md +4 -2
  105. package/src/schemas/entity.schema.json +28 -0
@@ -162,10 +162,13 @@ function unquoted(value) {
162
162
  * The logical grounding relationships an existing requirement already has for
163
163
  * this claim. A modeled claim takes exactly one grounding relationship, so a
164
164
  * second link (for example requires_predicate beside a bootstrap
165
- * requires_property) fails the proposition-complete rule.
165
+ * requires_property) fails the proposition-complete rule. A grounding fact is
166
+ * recognised by the claim's key (the same proposition text) or, for a
167
+ * predicate fact, by the recommended candidate's canonical_key (the same
168
+ * predicate tuple stated in other words).
166
169
  */
167
- // implements REQ-model-predicates-grounding-aware-v2
168
- async function existingClaimGrounding(prolog, requirementId, claimKey, warnings) {
170
+ // implements REQ-model-predicates-grounding-aware-v2, REQ-model-predicates-grounding-guards
171
+ async function existingClaimGrounding(prolog, requirementId, claimKey, canonicalKey, warnings) {
169
172
  if (prolog === null || !requirementId)
170
173
  return [];
171
174
  const found = [];
@@ -175,16 +178,27 @@ async function existingClaimGrounding(prolog, requirementId, claimKey, warnings)
175
178
  if (!targets.success)
176
179
  continue;
177
180
  for (const target of parsePrologList(targets.bindings.Targets ?? "[]")) {
178
- const fact = await prolog.query(`once((kb_entity('${escapeAtom(target)}', fact, _GroundProps), memberchk(claim_key=_GroundClaimRaw, _GroundProps), normalize_term_atom(_GroundClaimRaw, ClaimKey), (memberchk(fact_kind=_GroundKindRaw, _GroundProps) -> normalize_term_atom(_GroundKindRaw, FactKind) ; FactKind = '')))`);
181
+ const fact = await prolog.query(`once((kb_entity('${escapeAtom(target)}', fact, _GroundProps), memberchk(claim_key=_GroundClaimRaw, _GroundProps), normalize_term_atom(_GroundClaimRaw, ClaimKey), (memberchk(fact_kind=_GroundKindRaw, _GroundProps) -> normalize_term_atom(_GroundKindRaw, FactKind) ; FactKind = ''), (memberchk(canonical_key=_GroundCanonicalRaw, _GroundProps) -> normalize_term_atom(_GroundCanonicalRaw, CanonicalKey) ; CanonicalKey = '')))`);
179
182
  if (!fact.success)
180
183
  continue;
181
- if (unquoted(fact.bindings.ClaimKey) !== claimKey)
184
+ const storedClaimKey = unquoted(fact.bindings.ClaimKey);
185
+ const storedCanonicalKey = unquoted(fact.bindings.CanonicalKey);
186
+ const matchedBy = storedClaimKey === claimKey
187
+ ? "claim_key"
188
+ : canonicalKey !== null &&
189
+ storedCanonicalKey !== "" &&
190
+ storedCanonicalKey === canonicalKey
191
+ ? "canonical_key"
192
+ : null;
193
+ if (matchedBy === null)
182
194
  continue;
183
195
  found.push({
184
196
  relationship: { type, from: requirementId, to: target },
185
197
  factId: target,
186
198
  factKind: unquoted(fact.bindings.FactKind) || null,
187
- claimKey,
199
+ claimKey: storedClaimKey,
200
+ canonicalKey: storedCanonicalKey || null,
201
+ matchedBy,
188
202
  });
189
203
  }
190
204
  }
@@ -324,11 +338,30 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
324
338
  // Stage 3/4: bind only after eligibility, then decide conservatively.
325
339
  const unavailableSchema = Boolean(args.schemaId && selectedSchemas.length === 0);
326
340
  const claimKey = semanticClaimKey(text);
327
- const existingGrounding = await existingClaimGrounding(prolog, args.requirementId, claimKey, warnings);
341
+ const existingGrounding = await existingClaimGrounding(prolog, args.requirementId, claimKey, recommendedCandidate?.canonical_key ?? null, warnings);
328
342
  const alreadyGrounded = existingGrounding.length > 0;
343
+ // implements REQ-model-predicates-grounding-guards
344
+ // The requirement already grounds the very predicate tuple the candidate
345
+ // names (in other words, or the same text again): nothing to write or
346
+ // swap, whatever the candidate's binding status.
347
+ const sameTupleGrounding = existingGrounding.filter((row) => recommendedCandidate !== undefined &&
348
+ row.canonicalKey !== null &&
349
+ row.canonicalKey === recommendedCandidate.canonical_key);
350
+ const keepExistingGrounding = sameTupleGrounding.length > 0;
329
351
  const completeCandidate = recommendedCandidate && recommendedCandidate.binding_status === "complete"
330
352
  ? recommendedCandidate
331
353
  : undefined;
354
+ // The stored requirement and its proposition ledger, read once for the
355
+ // ledger guard, the replacement plan and the hash-bound plan.
356
+ const storedRequirement = prolog !== null && context !== undefined && args.requirementId
357
+ ? await storedRequirementContract(prolog, args.requirementId, warnings)
358
+ : null;
359
+ const ledger = ledgerState(storedRequirement, claimKey);
360
+ // A requirement grounds only the propositions its prose states, so a
361
+ // claim its ledger does not carry cannot be linked to it: the predicate
362
+ // fact would be written and stay unlinked. The requirement's prose has to
363
+ // be extended first.
364
+ const ledgerMissing = ledger === "missing" && completeCandidate !== undefined && !alreadyGrounded;
332
365
  // implements REQ-model-predicates-participant-not-subject
333
366
  // A schema whose participant argument (actor, role, owner) the claim never
334
367
  // names does not fit the claim: no binding the agent could supply would be
@@ -350,13 +383,17 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
350
383
  ? unavailableSchema
351
384
  ? "resolve_schema_reference"
352
385
  : "record_ontology_gap"
353
- : completeCandidate
354
- ? alreadyGrounded
355
- ? "replace_grounding"
356
- : "apply_requires_predicate"
357
- : schemaDoesNotFit
358
- ? "record_ontology_gap"
359
- : "provide_argument_bindings";
386
+ : keepExistingGrounding
387
+ ? "keep_existing_grounding"
388
+ : completeCandidate
389
+ ? alreadyGrounded
390
+ ? "replace_grounding"
391
+ : ledgerMissing
392
+ ? "extend_requirement"
393
+ : "apply_requires_predicate"
394
+ : schemaDoesNotFit
395
+ ? "record_ontology_gap"
396
+ : "provide_argument_bindings";
360
397
  const predicatePlan = completeCandidate
361
398
  ? buildPredicateApplyPlan(completeCandidate, args)
362
399
  : [];
@@ -365,18 +402,23 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
365
402
  // replacement plan below swaps the grounding instead. The ontology-gap
366
403
  // observation is not a grounding relationship, so it is planned either way.
367
404
  const applyPlan = completeCandidate
368
- ? alreadyGrounded
405
+ ? alreadyGrounded || ledgerMissing
369
406
  ? []
370
407
  : predicatePlan
371
408
  : (!recommendedCandidate && !unavailableSchema) || schemaDoesNotFit
372
409
  ? buildGapApplyPlan(text, args)
373
410
  : [];
374
411
  const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
375
- const relationshipPlan = completeCandidate && !alreadyGrounded
412
+ const relationshipPlan = completeCandidate && !alreadyGrounded && !ledgerMissing
376
413
  ? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, args.existingLogicClaims)
377
414
  : null;
378
- const replacementPlan = alreadyGrounded && plannedFactId !== null && args.requirementId
379
- ? buildGroundingReplacementPlan(args.requirementId, plannedFactId, predicatePlan, existingGrounding, await storedRequirementProperties(prolog, args.requirementId, warnings))
415
+ const replacementPlan = alreadyGrounded &&
416
+ !keepExistingGrounding &&
417
+ plannedFactId !== null &&
418
+ args.requirementId
419
+ ? buildGroundingReplacementPlan(args.requirementId, plannedFactId, predicatePlan, existingGrounding, storedRequirement !== null
420
+ ? restatedRequirementFields(storedRequirement)
421
+ : await storedRequirementProperties(prolog, args.requirementId, warnings))
380
422
  : null;
381
423
  const relationshipTarget = relationshipPlan !== null || replacementPlan !== null
382
424
  ? plannedFactId
@@ -384,9 +426,18 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
384
426
  const groundingSummary = existingGrounding
385
427
  .map((row) => `${row.relationship.type} -> ${row.factId}`)
386
428
  .join(", ");
387
- if (alreadyGrounded) {
429
+ const sameTupleSummary = sameTupleGrounding
430
+ .map((row) => `${row.relationship.type} -> ${row.factId}${row.matchedBy === "canonical_key" ? ` (matched by canonical_key; its claim_key ${row.claimKey} differs from this text's ${claimKey})` : ""}`)
431
+ .join(", ");
432
+ if (keepExistingGrounding) {
433
+ warnings.push(`${args.requirementId} already grounds the predicate ${recommendedCandidate?.canonical_key} through ${sameTupleSummary}. Keep that grounding: no fact or link is needed. If this text is a further proposition of the requirement, extend its prose and run kb_model mode analyze before grounding it separately.`);
434
+ }
435
+ else if (alreadyGrounded) {
388
436
  warnings.push(`${args.requirementId} already grounds this claim (${claimKey}) through ${groundingSummary}. A modeled claim takes exactly one logical grounding relationship, so adding requires_predicate beside it fails the proposition-complete rule. ${replacementPlan ? "Keep the existing grounding, or follow replacementPlan to swap it for the predicate." : "Keep the existing grounding; no predicate replacement is available yet."}`);
389
437
  }
438
+ if (ledger === "missing" && !alreadyGrounded && recommendedCandidate) {
439
+ warnings.push(`The proposition ledger of ${args.requirementId} has no claim ${claimKey} for this text, so the requirement cannot ground it: a requirement grounds only the propositions its prose states. ${ledgerMissing ? "No fact was planned, because it would be written without a link. " : ""}Extend the requirement's prose with this clause (kb_model mode requirement, or kb_upsert with the extended semantic_text and the ledger kb_model mode analyze returns), then call mode predicates again.`);
440
+ }
390
441
  // The hints stay on a schema that does not fit, so the agent can see which
391
442
  // participant the claim would have to name.
392
443
  const bindingHints = (recommendedAction === "provide_argument_bindings" || schemaDoesNotFit) &&
@@ -399,23 +450,27 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
399
450
  const recommendedPredicateSchema = !recommendedCandidate && !unavailableSchema
400
451
  ? buildPredicateSchemaDraft(text, subject)
401
452
  : null;
402
- const groundedPrefix = alreadyGrounded
453
+ const groundedPrefix = alreadyGrounded && !keepExistingGrounding
403
454
  ? `${args.requirementId} already grounds this claim through ${groundingSummary}. `
404
455
  : "";
405
456
  const textSummary = groundedPrefix +
406
- (replacementPlan
407
- ? `To use ${completeCandidate?.predicate_name} instead, follow replacementPlan and link requires_predicate to ${plannedFactId}; otherwise keep the existing grounding.`
408
- : completeCandidate
409
- ? `Suggested ${candidates.length} predicate candidate(s). Top applicable match: ${completeCandidate.predicate_name}. Apply the predicate fact ${plannedFactId}, then link requires_predicate to that fact id (not a candidate id).`
410
- : schemaDoesNotFit && recommendedCandidate
411
- ? `Matched ${recommendedCandidate.predicate_name}, but the claim names no ${unnamedParticipants.join(" or ")} (${describeBindingHints(bindingHints)}), so this schema does not fit the claim. Record the ontology-gap observation in applyPlan instead of inventing a participant; a predicate_schema whose arguments the claim names would fit.`
412
- : recommendedCandidate
413
- ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. Bind them from the claim text: ${describeBindingHints(bindingHints)}. A value that repeats an argument name or a stop word stays unbound. No apply plan was generated.`
414
- : unavailableSchema
415
- ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
416
- : alreadyGrounded
417
- ? "No predicate candidate passed the semantic applicability gate; keep the existing grounding and record the ontology-gap observation in applyPlan so the missing schema stays visible."
418
- : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
457
+ (keepExistingGrounding
458
+ ? `${args.requirementId} already grounds ${recommendedCandidate?.canonical_key} through ${sameTupleSummary}. Nothing to apply; keep that grounding.`
459
+ : replacementPlan
460
+ ? `To use ${completeCandidate?.predicate_name} instead, follow replacementPlan and link requires_predicate to ${plannedFactId}; otherwise keep the existing grounding.`
461
+ : ledgerMissing && completeCandidate
462
+ ? `Matched ${completeCandidate.predicate_name} as ${completeCandidate.canonical_key}, but ${args.requirementId} does not state this claim (its ledger has no ${claimKey}). Extend the requirement's prose with this clause first; no fact or link was planned.`
463
+ : completeCandidate
464
+ ? `Suggested ${candidates.length} predicate candidate(s). Top applicable match: ${completeCandidate.predicate_name}. Apply the predicate fact ${plannedFactId}, then link requires_predicate to that fact id (not a candidate id).`
465
+ : schemaDoesNotFit && recommendedCandidate
466
+ ? `Matched ${recommendedCandidate.predicate_name}, but the claim names no ${unnamedParticipants.join(" or ")} (${describeBindingHints(bindingHints)}), so this schema does not fit the claim. Record the ontology-gap observation in applyPlan instead of inventing a participant; a predicate_schema whose arguments the claim names would fit.`
467
+ : recommendedCandidate
468
+ ? `Matched ${recommendedCandidate.predicate_name}, but exact reviewed values are still required for: ${recommendedCandidate.unbound_arguments.join(", ")}. Bind them from the claim text: ${describeBindingHints(bindingHints)}. A value that repeats an argument name or a stop word stays unbound. No apply plan was generated.`
469
+ : unavailableSchema
470
+ ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
471
+ : alreadyGrounded
472
+ ? "No predicate candidate passed the semantic applicability gate; keep the existing grounding and record the ontology-gap observation in applyPlan so the missing schema stays visible."
473
+ : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
419
474
  const logicClaims = Array.from(new Set([...(args.existingLogicClaims ?? []), claimKey]));
420
475
  const plan = relationshipPlan !== null && plannedFactId !== null && args.requirementId
421
476
  ? await groundingPlan(prolog, context, {
@@ -425,9 +480,21 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
425
480
  factStep: predicatePlan[0] ?? {},
426
481
  relationship: relationshipPlan.relationship,
427
482
  logicClaims,
483
+ stored: storedRequirement,
428
484
  warnings,
429
485
  })
430
486
  : null;
487
+ // Without a plan nothing ran the contradiction analysis, so the decision
488
+ // conflicts a complete candidate would introduce are looked up directly.
489
+ const decisionConflicts = plan === null &&
490
+ completeCandidate !== undefined &&
491
+ !keepExistingGrounding &&
492
+ args.requirementId
493
+ ? await candidateDecisionConflicts(prolog, completeCandidate, warnings)
494
+ : [];
495
+ for (const conflict of decisionConflicts) {
496
+ warnings.push(`The proposed ${conflict.canonicalKey} (${conflict.proposedEffect}) contradicts ${conflict.factId} (${conflict.effect}) under ${conflict.requirementId}${conflict.requirementId === args.requirementId ? ", the requirement this claim would ground" : ""}: both decide ${completeCandidate?.predicate_name} on the same key arguments. kb_upsert refuses a grounding link that introduces this conflict; supersede or close the conflicting requirement, or correct the decision, before applying.`);
497
+ }
431
498
  return {
432
499
  content: [{ type: "text", text: textSummary }],
433
500
  structuredContent: {
@@ -445,6 +512,7 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
445
512
  plan,
446
513
  relationshipTarget,
447
514
  existingGrounding,
515
+ decisionConflicts,
448
516
  replacementPlan,
449
517
  bindingHints,
450
518
  warnings,
@@ -452,6 +520,67 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
452
520
  applyPlan,
453
521
  };
454
522
  }
523
+ /**
524
+ * Whether the stored requirement's proposition ledger carries the claim:
525
+ * "unknown" when the requirement or its ledger is not readable.
526
+ */
527
+ // implements REQ-model-predicates-grounding-guards
528
+ function ledgerState(stored, claimKey) {
529
+ if (stored === null || !Array.isArray(stored.semantic_inventory))
530
+ return "unknown";
531
+ return stored.semantic_inventory.some((row) => row !== null &&
532
+ typeof row === "object" &&
533
+ row.claim_key === claimKey)
534
+ ? "present"
535
+ : "missing";
536
+ }
537
+ /**
538
+ * Stored decision-predicate facts of current requirements that agree with
539
+ * the candidate on every key argument and decide the opposite: the pairs
540
+ * the domain-contradictions rule would report once the candidate is linked.
541
+ */
542
+ // implements REQ-model-predicates-grounding-guards
543
+ async function candidateDecisionConflicts(prolog, candidate, warnings) {
544
+ if (prolog === null)
545
+ return [];
546
+ if (candidate.predicate_args.length === 0 ||
547
+ candidate.predicate_args.some((value) => !value))
548
+ return [];
549
+ const candidateArgs = `[${candidate.predicate_args
550
+ .map((value) => `'${escapeAtom(value)}'`)
551
+ .join(",")}]`;
552
+ try {
553
+ const result = await prolog.query(`findall(Row, (kb:decision_signatures(_Signatures), memberchk(decision_signature(${candidate.schema.predicate_namespace ? `'${escapeAtom(candidate.schema.predicate_namespace)}'` : "_Namespace"}, '${escapeAtom(candidate.predicate_name)}', ${candidate.predicate_args.length}, _KeyPositions, _DecisionPosition), _Signatures), nth1(_DecisionPosition, ${candidateArgs}, _ProposedDecision), kb:effective_decision(_ProposedDecision, ${candidate.polarity}, _ProposedEffect), kb:decision_req_fact(_Signatures, _Req, _Fact, ${candidate.schema.predicate_namespace ? `'${escapeAtom(candidate.schema.predicate_namespace)}'` : "_Namespace"}, '${escapeAtom(candidate.predicate_name)}', _Args, _Polarity, _KeyPositions, _DecisionPosition), kb:bind_key_arguments(_KeyPositions, ${candidateArgs}, _Args), nth1(_DecisionPosition, _Args, _Decision), kb:effective_decision(_Decision, _Polarity, _Effect), _Effect \\== _ProposedEffect, atomic_list_concat([_Req, _Fact, _Effect, _ProposedEffect], '|', Row)), Rows)`);
554
+ if (!result.success)
555
+ return [];
556
+ const conflicts = [];
557
+ for (const row of parsePrologList(result.bindings.Rows ?? "[]")) {
558
+ const [requirementId, factId, effect, proposedEffect] = row.split("|");
559
+ if (!requirementId || !factId || !effect || !proposedEffect)
560
+ continue;
561
+ conflicts.push({
562
+ requirementId,
563
+ factId,
564
+ effect,
565
+ proposedEffect,
566
+ canonicalKey: candidate.canonical_key,
567
+ });
568
+ }
569
+ return conflicts.sort((left, right) => `${left.requirementId}|${left.factId}`.localeCompare(`${right.requirementId}|${right.factId}`));
570
+ }
571
+ catch (error) {
572
+ warnings.push(`Decision conflicts for ${candidate.canonical_key} could not be read (${error instanceof Error ? error.message : String(error)}); preview the grounding with kb_upsert dryRun and plannedFacts before applying.`);
573
+ return [];
574
+ }
575
+ }
576
+ /** The stored requirement fields a relationship-only update must restate. */
577
+ function restatedRequirementFields(stored) {
578
+ const restated = {};
579
+ for (const field of RESTATED_REQUIREMENT_FIELDS)
580
+ if (stored[field] !== undefined && stored[field] !== null)
581
+ restated[field] = stored[field];
582
+ return restated;
583
+ }
455
584
  /**
456
585
  * Why the recommended predicate is not offered for grounding: it is not about
457
586
  * a subject the requirement constrains, so kb_check would keep reporting
@@ -484,7 +613,7 @@ const RESTATED_REQUIREMENT_FIELDS = [
484
613
  async function groundingPlan(prolog, context, input) {
485
614
  if (prolog === null || context === undefined)
486
615
  return null;
487
- const stored = await storedRequirementContract(prolog, input.requirementId, input.warnings);
616
+ const stored = input.stored === null ? null : { ...input.stored };
488
617
  if (stored === null) {
489
618
  input.warnings.push(`No hash-bound plan: requirement ${input.requirementId} is not stored, so its upsert step cannot restate the stored title and status. Apply applyPlan and relationshipPlan with kb_upsert.`);
490
619
  return null;
@@ -521,7 +650,17 @@ async function groundingPlan(prolog, context, input) {
521
650
  {
522
651
  type: "req",
523
652
  id: input.requirementId,
524
- properties: { ...stored, logic_claims: [...input.logicClaims] },
653
+ // The manifest must name every assertive ledger claim, so the
654
+ // stored manifest is kept and this claim merged into it.
655
+ properties: {
656
+ ...stored,
657
+ logic_claims: Array.from(new Set([
658
+ ...(Array.isArray(stored.logic_claims)
659
+ ? stored.logic_claims.filter((claim) => typeof claim === "string")
660
+ : []),
661
+ ...input.logicClaims,
662
+ ])),
663
+ },
525
664
  relationships: [input.relationship],
526
665
  },
527
666
  ],
@@ -550,6 +689,7 @@ const STORED_LEDGER_FIELDS = [
550
689
  "semantic_inventory_version",
551
690
  "semantic_source_field",
552
691
  "semantic_source_hash",
692
+ "logic_claims",
553
693
  ];
554
694
  // implements REQ-model-what-if-planned-facts
555
695
  /**
@@ -3,6 +3,10 @@ import type { OperationResult } from "../../public/operations/types.js";
3
3
  import { type RelationshipSelector } from "../../relationships/shards.js";
4
4
  import type { DeleteInput, DeletePayload } from "./types.js";
5
5
  export declare function assertFilesystemCapableRuntime(fs: OperationContext["fs"]): asserts fs is NonNullable<OperationContext["fs"]>;
6
- export declare function executeRelationshipDelete(selectors: readonly RelationshipSelector[], context: OperationContext, onCommitted?: () => void): Promise<DeletePayload>;
6
+ type DeleteExecutionOptions = Readonly<{
7
+ dryRun?: boolean;
8
+ }>;
9
+ export declare function executeRelationshipDelete(selectors: readonly RelationshipSelector[], context: OperationContext, onCommitted?: () => void, options?: DeleteExecutionOptions): Promise<DeletePayload>;
7
10
  export declare function executeDelete(input: DeleteInput, context: OperationContext): Promise<OperationResult<DeletePayload>>;
11
+ export {};
8
12
  //# sourceMappingURL=delete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/delete.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EACL,KAAK,oBAAoB,EAM1B,MAAM,+BAA+B,CAAC;AAqBvC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7D,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,GACzB,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAMnD;AAuWD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,SAAS,oBAAoB,EAAE,EAC1C,OAAO,EAAE,gBAAgB,EACzB,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAuSzC"}
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/delete.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EACL,KAAK,oBAAoB,EAO1B,MAAM,+BAA+B,CAAC;AAqBvC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7D,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,GACzB,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAMnD;AA2CD,KAAK,sBAAsB,GAAG,QAAQ,CAAC;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAwZ7D,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,SAAS,oBAAoB,EAAE,EAC1C,OAAO,EAAE,gBAAgB,EACzB,WAAW,GAAE,MAAM,IAAsB,EACzC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,aAAa,CAAC,CAkCxB;AAoHD,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAkXzC"}