kibi-cli 2.13.0 → 2.15.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.
Files changed (114) hide show
  1. package/dist/engine.d.ts.map +1 -1
  2. package/dist/engine.js +5 -2
  3. package/dist/intent-search.d.ts.map +1 -1
  4. package/dist/intent-search.js +16 -4
  5. package/dist/operations/bootstrap/candidate-helpers.d.ts +5 -0
  6. package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
  7. package/dist/operations/bootstrap/candidate-helpers.js +8 -0
  8. package/dist/operations/bootstrap/candidates.d.ts +12 -1
  9. package/dist/operations/bootstrap/candidates.d.ts.map +1 -1
  10. package/dist/operations/bootstrap/candidates.js +41 -7
  11. package/dist/operations/bootstrap/discovery-evidence.d.ts.map +1 -1
  12. package/dist/operations/bootstrap/discovery-evidence.js +8 -0
  13. package/dist/operations/bootstrap/discovery.d.ts.map +1 -1
  14. package/dist/operations/bootstrap/discovery.js +6 -1
  15. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  16. package/dist/operations/bootstrap/generate.js +18 -5
  17. package/dist/operations/bootstrap/guidance-actions.d.ts +2 -1
  18. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  19. package/dist/operations/bootstrap/guidance-actions.js +10 -1
  20. package/dist/operations/bootstrap/guidance.d.ts +2 -0
  21. package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
  22. package/dist/operations/bootstrap/guidance.js +1 -1
  23. package/dist/operations/bootstrap/plugin-offers.d.ts +16 -0
  24. package/dist/operations/bootstrap/plugin-offers.d.ts.map +1 -0
  25. package/dist/operations/bootstrap/plugin-offers.js +66 -0
  26. package/dist/operations/bootstrap/presentation.d.ts +2 -0
  27. package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
  28. package/dist/operations/bootstrap/presentation.js +19 -5
  29. package/dist/operations/bootstrap/types.d.ts +8 -0
  30. package/dist/operations/bootstrap/types.d.ts.map +1 -1
  31. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  32. package/dist/operations/modeling/predicate-applicability.js +27 -1
  33. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  34. package/dist/operations/modeling/predicate-applyplan.js +9 -5
  35. package/dist/operations/modeling/predicate-binding-hints.d.ts +10 -0
  36. package/dist/operations/modeling/predicate-binding-hints.d.ts.map +1 -1
  37. package/dist/operations/modeling/predicate-binding-hints.js +112 -16
  38. package/dist/operations/modeling/predicate-bindings.d.ts +16 -0
  39. package/dist/operations/modeling/predicate-bindings.d.ts.map +1 -1
  40. package/dist/operations/modeling/predicate-bindings.js +40 -1
  41. package/dist/operations/modeling/predicate-catalog-5.d.ts +2 -2
  42. package/dist/operations/modeling/predicate-catalog-5.js +2 -2
  43. package/dist/operations/modeling/predicate-catalog-6.d.ts +9 -0
  44. package/dist/operations/modeling/predicate-catalog-6.d.ts.map +1 -0
  45. package/dist/operations/modeling/predicate-catalog-6.js +106 -0
  46. package/dist/operations/modeling/predicate-catalog.d.ts.map +1 -1
  47. package/dist/operations/modeling/predicate-catalog.js +2 -0
  48. package/dist/operations/modeling/predicate-usage-hints-4.d.ts.map +1 -1
  49. package/dist/operations/modeling/predicate-usage-hints-4.js +32 -0
  50. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  51. package/dist/operations/modeling/suggest-predicates.js +48 -17
  52. package/dist/operations/mutation/contradictions.d.ts +8 -0
  53. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  54. package/dist/operations/mutation/contradictions.js +13 -0
  55. package/dist/operations/mutation/source-authoring.d.ts +13 -0
  56. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  57. package/dist/operations/mutation/source-authoring.js +70 -2
  58. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  59. package/dist/operations/mutation/upsert.js +9 -3
  60. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  61. package/dist/operations/mutation/validate-upsert.js +30 -4
  62. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
  63. package/dist/operations/semantic-advisor/ingestion-boundary.js +20 -1
  64. package/dist/plugins/approved-source-analyzers.generated.d.ts +5 -5
  65. package/dist/plugins/approved-source-analyzers.generated.js +5 -5
  66. package/dist/plugins/check-policies.d.ts +24 -0
  67. package/dist/plugins/check-policies.d.ts.map +1 -0
  68. package/dist/plugins/check-policies.js +73 -0
  69. package/dist/plugins/registry.d.ts.map +1 -1
  70. package/dist/plugins/registry.js +10 -1
  71. package/dist/prolog.d.ts.map +1 -1
  72. package/dist/prolog.js +8 -1
  73. package/dist/provenance-stub.d.ts +15 -0
  74. package/dist/provenance-stub.d.ts.map +1 -0
  75. package/dist/provenance-stub.js +30 -0
  76. package/dist/public/operations/catalog.d.ts +1 -1
  77. package/dist/public/operations/check-executor.d.ts.map +1 -1
  78. package/dist/public/operations/check-executor.js +3 -0
  79. package/dist/public/operations/check-policy-rules.d.ts +49 -0
  80. package/dist/public/operations/check-policy-rules.d.ts.map +1 -0
  81. package/dist/public/operations/check-policy-rules.js +305 -0
  82. package/dist/public/operations/contracts.d.ts.map +1 -1
  83. package/dist/public/operations/contracts.js +5 -0
  84. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
  85. package/dist/public/operations/source-relationship-parity.js +16 -1
  86. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  87. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  88. package/dist/public/operations/specs/check-rules.generated.js +3 -1
  89. package/dist/public/operations/specs/check.d.ts +1 -1
  90. package/dist/public/operations/specs/reporting.d.ts +9 -0
  91. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  92. package/dist/public/operations/specs/reporting.js +6 -2
  93. package/dist/public/skills/kibi-bootstrap/SKILL.md +3 -3
  94. package/dist/public/skills/kibi-usage/SKILL.md +81 -5
  95. package/dist/public/skills/kibi-usage/resources/ui-requirements.md +42 -3
  96. package/dist/report/html-report.d.ts.map +1 -1
  97. package/dist/report/html-report.js +13 -2
  98. package/dist/search-answer.d.ts.map +1 -1
  99. package/dist/search-answer.js +7 -0
  100. package/dist/search-ranking.d.ts.map +1 -1
  101. package/dist/search-ranking.js +9 -1
  102. package/dist/utils/rule-registry.generated.d.ts +10 -0
  103. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  104. package/dist/utils/rule-registry.generated.js +12 -0
  105. package/package.json +4 -4
  106. package/src/public/operations/check-executor.ts +7 -0
  107. package/src/public/operations/check-policy-rules.ts +462 -0
  108. package/src/public/operations/contracts.ts +6 -0
  109. package/src/public/operations/source-relationship-parity.ts +17 -2
  110. package/src/public/operations/specs/check-rules.generated.ts +3 -1
  111. package/src/public/operations/specs/reporting.ts +13 -2
  112. package/src/public/skills/kibi-bootstrap/SKILL.md +3 -3
  113. package/src/public/skills/kibi-usage/SKILL.md +81 -5
  114. package/src/public/skills/kibi-usage/resources/ui-requirements.md +42 -3
@@ -0,0 +1,106 @@
1
+ /**
2
+ * UI design patterns: which visual pattern a concept uses, which variants
3
+ * must share it, which code markers make it recognisable, and the layout
4
+ * shell it sits in. `kibi-plugin-ui` enforces these through its check policy;
5
+ * without that plugin they are ordinary predicates.
6
+ */
7
+ // implements REQ-mcp-suggest-predicates, REQ-ui-pattern-vocabulary
8
+ export const PREDICATE_CATALOG_6 = [
9
+ {
10
+ id: "FACT-SCHEMA-UI-PATTERN",
11
+ predicate_name: "ui_pattern",
12
+ title: "UI pattern",
13
+ description: "A UI concept must be presented with a named visual pattern, such as a line-and-dots timeline or a card list.",
14
+ argument_names: ["subject", "pattern"],
15
+ argument_types: ["ui_entity", "ui_pattern"],
16
+ argument_descriptions: [
17
+ "The UI concept or region, such as an annotation list.",
18
+ "The agreed visual pattern for that concept.",
19
+ ],
20
+ keywords: [
21
+ "pattern",
22
+ "design pattern",
23
+ "presented as",
24
+ "rendered as",
25
+ "displayed as",
26
+ "timeline",
27
+ "card list",
28
+ "layout pattern",
29
+ ],
30
+ aliases: ["visual pattern", "design direction"],
31
+ examples: ["ui_pattern(annotation_list, line_dots_timeline)"],
32
+ tags: ["ui", "design", "pattern"],
33
+ },
34
+ {
35
+ id: "FACT-SCHEMA-SAME-PATTERN",
36
+ predicate_name: "same_pattern",
37
+ title: "Same pattern across variants",
38
+ description: "Two variants of a UI concept (roles, viewports, presentations) must use the same visual pattern.",
39
+ argument_names: ["subject", "variant", "other_variant"],
40
+ argument_types: ["ui_entity", "ui_variant", "ui_variant"],
41
+ argument_descriptions: [
42
+ "The UI concept whose variants must not diverge.",
43
+ "One variant, such as a role or presentation.",
44
+ "The variant that must match it.",
45
+ ],
46
+ keywords: [
47
+ "same pattern",
48
+ "same design",
49
+ "consistent",
50
+ "for both",
51
+ "same timeline",
52
+ "must match",
53
+ ],
54
+ aliases: ["consistent across roles", "no divergent variant"],
55
+ examples: ["same_pattern(annotation_list, editor_view, read_only_view)"],
56
+ tags: ["ui", "design", "consistency"],
57
+ },
58
+ {
59
+ id: "FACT-SCHEMA-PATTERN-MARKER",
60
+ predicate_name: "pattern_marker",
61
+ title: "Pattern marker",
62
+ description: "A visual pattern is recognisable in code by a stable marker such as a CSS class or test id.",
63
+ argument_names: ["subject", "marker"],
64
+ argument_types: ["ui_pattern", "ui_marker"],
65
+ argument_descriptions: [
66
+ "The visual pattern the marker belongs to.",
67
+ "Literal class name or test id that implementations of the pattern contain.",
68
+ ],
69
+ keywords: [
70
+ "marker",
71
+ "marked by",
72
+ "class name",
73
+ "test id",
74
+ "data-testid",
75
+ "css class",
76
+ ],
77
+ aliases: ["pattern anchor"],
78
+ examples: ["pattern_marker(line_dots_timeline, timeline_dot)"],
79
+ tags: ["ui", "design", "pattern"],
80
+ },
81
+ {
82
+ id: "FACT-SCHEMA-UI-CONTAINER",
83
+ predicate_name: "ui_container",
84
+ title: "UI container",
85
+ description: "A UI region must sit in a container of a given size with a given overflow behaviour.",
86
+ argument_names: ["subject", "size", "overflow"],
87
+ argument_types: ["ui_entity", "ui_size", "ui_overflow"],
88
+ argument_descriptions: [
89
+ "The UI region the container holds.",
90
+ "Container size, such as half_page or full_height.",
91
+ "Overflow behaviour, such as scroll or clip.",
92
+ ],
93
+ keywords: [
94
+ "container",
95
+ "half page",
96
+ "half-page",
97
+ "scrollbar",
98
+ "scrollable",
99
+ "panel",
100
+ "overflow",
101
+ ],
102
+ aliases: ["layout shell"],
103
+ examples: ["ui_container(review_panel, half_page, scroll)"],
104
+ tags: ["ui", "design", "layout"],
105
+ },
106
+ ];
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-catalog.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-catalog.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAO9B,eAAO,MAAM,mBAAmB,EAAE,mBAOjC,CAAC;AAGF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAKxE,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,wBAAwB,EAMhE,CAAC"}
1
+ {"version":3,"file":"predicate-catalog.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-catalog.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAO9B,eAAO,MAAM,mBAAmB,EAAE,mBAOjC,CAAC;AAGF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAKxE,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,wBAAwB,EAOhE,CAAC"}
@@ -3,6 +3,7 @@ import { PREDICATE_CATALOG_2 } from "./predicate-catalog-2.js";
3
3
  import { PREDICATE_CATALOG_3 } from "./predicate-catalog-3.js";
4
4
  import { PREDICATE_CATALOG_4 } from "./predicate-catalog-4.js";
5
5
  import { PREDICATE_CATALOG_5 } from "./predicate-catalog-5.js";
6
+ import { PREDICATE_CATALOG_6 } from "./predicate-catalog-6.js";
6
7
  import { PREDICATE_USAGE_HINTS_1 } from "./predicate-usage-hints-1.js";
7
8
  import { PREDICATE_USAGE_HINTS_2 } from "./predicate-usage-hints-2.js";
8
9
  import { PREDICATE_USAGE_HINTS_3 } from "./predicate-usage-hints-3.js";
@@ -30,4 +31,5 @@ export const BUILT_IN_PREDICATE_SCHEMAS = [
30
31
  ...PREDICATE_CATALOG_3,
31
32
  ...PREDICATE_CATALOG_4,
32
33
  ...PREDICATE_CATALOG_5,
34
+ ...PREDICATE_CATALOG_6,
33
35
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-usage-hints-4.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-usage-hints-4.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAkFvE,CAAC"}
1
+ {"version":3,"file":"predicate-usage-hints-4.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-usage-hints-4.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAkHvE,CAAC"}
@@ -81,4 +81,36 @@ export const PREDICATE_USAGE_HINTS_4 = {
81
81
  ],
82
82
  // implements REQ-mcp-suggest-predicates
83
83
  },
84
+ ui_pattern: {
85
+ use_when: [
86
+ "Use when a UI concept must be presented with a named visual pattern that other screens or variants must not replace.",
87
+ ],
88
+ do_not_use_when: [
89
+ "Do not use for alignment between two elements (visual_layout_rule) or for colours and spacing a linter can check.",
90
+ ],
91
+ },
92
+ same_pattern: {
93
+ use_when: [
94
+ "Use when two variants of one UI concept, such as roles or presentations, must render the same visual pattern.",
95
+ ],
96
+ do_not_use_when: [
97
+ "Do not use when the variants are intentionally different; record that difference in the requirement instead.",
98
+ ],
99
+ },
100
+ pattern_marker: {
101
+ use_when: [
102
+ "Use to name a stable class or test id that every implementation of a visual pattern contains.",
103
+ ],
104
+ do_not_use_when: [
105
+ "Do not use for markers that change with content or styling, such as generated class names.",
106
+ ],
107
+ },
108
+ ui_container: {
109
+ use_when: [
110
+ "Use when a UI region must sit in a container of a named size with a named overflow behaviour, such as a half-page scroll panel.",
111
+ ],
112
+ do_not_use_when: [
113
+ "Do not use for exact pixel sizes or brand tokens a linter or stylesheet already enforces.",
114
+ ],
115
+ },
84
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AAyBlD,OAAO,KAAK,EAGV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA6H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AA+HD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAwRlC;AAkID,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
1
+ {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AA0BlD,OAAO,KAAK,EAGV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA6H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AA+HD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAmTlC;AA2ID,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
@@ -6,7 +6,7 @@ import { semanticClaimKey } from "../semantic-advisor/clauses.js";
6
6
  import { composeOntologyCatalogForOperation } from "../semantic-advisor/plugin-orchestration.js";
7
7
  import { STRONG_APPLICABILITY_SCORE, WEAK_CANDIDATE_MARGIN, evaluateSemanticApplicability, } from "./predicate-applicability.js";
8
8
  import { buildGapApplyPlan, buildPredicateApplyPlan, buildPredicateSchemaDraft, buildRelationshipPlan, buildSuggestion, } from "./predicate-applyplan.js";
9
- import { buildBindingHints, describeBindingHints, } from "./predicate-binding-hints.js";
9
+ import { buildBindingHints, describeBindingHints, unnamedParticipantArguments, } from "./predicate-binding-hints.js";
10
10
  import { BUILT_IN_PREDICATE_SCHEMAS } from "./predicate-catalog.js";
11
11
  import { inferSubject } from "./predicate-inference.js";
12
12
  import { loadExistingPredicateSchemas } from "./predicate-loader.js";
@@ -326,6 +326,20 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
326
326
  const completeCandidate = recommendedCandidate && recommendedCandidate.binding_status === "complete"
327
327
  ? recommendedCandidate
328
328
  : undefined;
329
+ // implements REQ-model-predicates-participant-not-subject
330
+ // A schema whose participant argument (actor, role, owner) the claim never
331
+ // names does not fit the claim: no binding the agent could supply would be
332
+ // read from the claim. When every unbound argument of the recommended
333
+ // candidate is such a participant, the claim is an ontology gap, not a
334
+ // binding request. An argument unbound for another reason (a value the
335
+ // claim does name but in other words, a closed vocabulary) still asks for
336
+ // bindings.
337
+ const unnamedParticipants = recommendedCandidate && !completeCandidate
338
+ ? unnamedParticipantArguments(recommendedCandidate, text, requirementSubjects)
339
+ : [];
340
+ const schemaDoesNotFit = unnamedParticipants.length > 0 &&
341
+ unnamedParticipants.length ===
342
+ (recommendedCandidate?.unbound_arguments.length ?? 0);
329
343
  // The action names the predicate state; existingGrounding (non-empty) is
330
344
  // the separate "already grounded" signal. A complete candidate for a claim
331
345
  // that is already grounded becomes a replacement, never a second link.
@@ -337,7 +351,9 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
337
351
  ? alreadyGrounded
338
352
  ? "replace_grounding"
339
353
  : "apply_requires_predicate"
340
- : "provide_argument_bindings";
354
+ : schemaDoesNotFit
355
+ ? "record_ontology_gap"
356
+ : "provide_argument_bindings";
341
357
  const predicatePlan = completeCandidate
342
358
  ? buildPredicateApplyPlan(completeCandidate, args)
343
359
  : [];
@@ -349,7 +365,7 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
349
365
  ? alreadyGrounded
350
366
  ? []
351
367
  : predicatePlan
352
- : !recommendedCandidate && !unavailableSchema
368
+ : (!recommendedCandidate && !unavailableSchema) || schemaDoesNotFit
353
369
  ? buildGapApplyPlan(text, args)
354
370
  : [];
355
371
  const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
@@ -368,7 +384,10 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
368
384
  if (alreadyGrounded) {
369
385
  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."}`);
370
386
  }
371
- const bindingHints = recommendedAction === "provide_argument_bindings" && recommendedCandidate
387
+ // The hints stay on a schema that does not fit, so the agent can see which
388
+ // participant the claim would have to name.
389
+ const bindingHints = (recommendedAction === "provide_argument_bindings" || schemaDoesNotFit) &&
390
+ recommendedCandidate
372
391
  ? buildBindingHints(recommendedCandidate, text, requirementSubjects)
373
392
  : [];
374
393
  if (recommendedCandidate?.subject_pairing === "unpaired") {
@@ -385,13 +404,15 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
385
404
  ? `To use ${completeCandidate?.predicate_name} instead, follow replacementPlan and link requires_predicate to ${plannedFactId}; otherwise keep the existing grounding.`
386
405
  : completeCandidate
387
406
  ? `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).`
388
- : recommendedCandidate
389
- ? `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.`
390
- : unavailableSchema
391
- ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
392
- : alreadyGrounded
393
- ? "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."
394
- : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
407
+ : schemaDoesNotFit && recommendedCandidate
408
+ ? `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.`
409
+ : recommendedCandidate
410
+ ? `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.`
411
+ : unavailableSchema
412
+ ? `Requested predicate schema ${args.schemaId} is unavailable or semantically inapplicable. No apply plan was generated.`
413
+ : alreadyGrounded
414
+ ? "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."
415
+ : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
395
416
  const logicClaims = Array.from(new Set([...(args.existingLogicClaims ?? []), claimKey]));
396
417
  return {
397
418
  content: [{ type: "text", text: textSummary }],
@@ -475,11 +496,13 @@ async function storedRequirementProperties(prolog, requirementId, warnings) {
475
496
  * fact, retract the old grounding link, then link requires_predicate. The
476
497
  * order is forced: kb_upsert merges a requirement's relationships, so linking
477
498
  * first would give the claim two groundings and be rejected. Between the
478
- * retraction and the link the claim is ungrounded (kb_check reports
479
- * logic-coverage), so the steps run back to back and `rollback` restores the
480
- * old link if the last step fails.
499
+ * retraction and the link the claim is ungrounded and its subject fact is
500
+ * unpaired (kb_check reports logic-coverage and strict-req-fact-pairing for
501
+ * the requirement), so the steps run back to back; `expected` states those
502
+ * transient diagnostics and `rollback` restores the old link when kb_check
503
+ * after the last step is not clean.
481
504
  */
482
- // implements REQ-model-predicates-grounding-aware-v2, REQ-model-predicates-plan-roundtrip
505
+ // implements REQ-model-predicates-grounding-aware-v2, REQ-model-predicates-plan-roundtrip, REQ-model-predicates-replacement-diagnostics
483
506
  function buildGroundingReplacementPlan(requirementId, factId, predicatePlan, existing, stored) {
484
507
  const requirementUpsert = (relationships) => ({
485
508
  type: "req",
@@ -490,8 +513,16 @@ function buildGroundingReplacementPlan(requirementId, factId, predicatePlan, exi
490
513
  const restate = stored === null
491
514
  ? `add the requirement's stored title and status as properties (they could not be read), keep this relationship only`
492
515
  : "the properties restate the stored title, status and metadata; kb_upsert keeps the stored proposition ledger";
516
+ const transientRules = ["logic-coverage", "strict-req-fact-pairing"];
493
517
  return {
494
518
  relationshipTarget: factId,
519
+ expected: {
520
+ // kb_check findings for the requirement after each step, by rule name
521
+ // (violations carry `rule`, quality diagnostics `rule.<name>`).
522
+ kbCheckAfterStep: [[], transientRules, []],
523
+ kbCheckAfterLastStep: [],
524
+ rollbackWhen: "kb_check after the last step is not clean",
525
+ },
495
526
  steps: [
496
527
  {
497
528
  operation: "kb_upsert",
@@ -516,9 +547,9 @@ function buildGroundingReplacementPlan(requirementId, factId, predicatePlan, exi
516
547
  rollback: {
517
548
  operation: "kb_upsert",
518
549
  input: requirementUpsert(existing.map((row) => row.relationship)),
519
- reason: "Only if the last step fails: restore the retracted grounding link so the claim is grounded again.",
550
+ reason: `Only if kb_check after the last step is not clean (it still reports ${transientRules.join(" or ")} for ${requirementId}, or the step failed): restore the retracted grounding link so the claim is grounded again.`,
520
551
  },
521
- instructions: `Only replace the grounding when the predicate states the claim at least as precisely as ${existing.map((row) => row.factId).join(", ")}. Apply the steps unchanged, in order and back to back: linking requires_predicate before the retraction is rejected because the claim would have two groundings, and between the retraction and the link the claim is ungrounded, so kb_check reports logic-coverage for ${requirementId} until the last step lands. If the last step fails, apply rollback. Run kb_check after the last step.`,
552
+ instructions: `Only replace the grounding when the predicate states the claim at least as precisely as ${existing.map((row) => row.factId).join(", ")}. Apply the steps unchanged, in order and back to back: linking requires_predicate before the retraction is rejected because the claim would have two groundings, and between the retraction and the link the claim is ungrounded and its subject unpaired, so kb_check reports both ${transientRules.join(" and ")} for ${requirementId} until the last step lands (see expected.kbCheckAfterStep). Run kb_check after the last step; if it is not clean, apply rollback.`,
522
553
  };
523
554
  }
524
555
  export async function executeSuggestPredicates(args, context) {
@@ -6,6 +6,14 @@ type TransactionInput = {
6
6
  readonly skipContradictionCheck: boolean;
7
7
  };
8
8
  export declare function buildUpsertCommitGoal(input: TransactionInput): string;
9
+ /**
10
+ * Goal that stages the same entity and relationships the commit goal would
11
+ * and runs the commit-time contradiction check inside a transaction that is
12
+ * always rolled back (kb_preview_upsert_contradiction/3). It succeeds when
13
+ * the commit would pass the check and fails with the commit's error when it
14
+ * would not; it never writes.
15
+ */
16
+ export declare function buildUpsertContradictionPreviewGoal(input: TransactionInput): string;
9
17
  type WhatIfInput = TransactionInput & {
10
18
  readonly relationshipsOnly?: boolean;
11
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"contradictions.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/contradictions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AASrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C,CAAC;AAGF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAOrE;AAiBD,KAAK,WAAW,GAAG,gBAAgB,GAAG;IAAE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAgB/E;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,MAAM,CAER;AAGD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,MAAM,CAER;AAED,oEAAoE;AAEpE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,MAAM,CAKR;AAGD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC1C,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC,CAe9B;AAGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,iBAAiB,GACzB,MAAM,CAOR"}
1
+ {"version":3,"file":"contradictions.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/contradictions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AASrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C,CAAC;AAGF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAOrE;AAGD;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,gBAAgB,GACtB,MAAM,CAOR;AAiBD,KAAK,WAAW,GAAG,gBAAgB,GAAG;IAAE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAgB/E;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,MAAM,CAER;AAGD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,MAAM,CAER;AAED,oEAAoE;AAEpE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,MAAM,CAKR;AAGD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC1C,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC,CAe9B;AAGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,iBAAiB,GACzB,MAAM,CAOR"}
@@ -7,6 +7,19 @@ export function buildUpsertCommitGoal(input) {
7
7
  const relationships = input.relationships.map((relationship) => `rel(${String(relationship.type)}, '${escapeAtom(String(relationship.from))}', '${escapeAtom(String(relationship.to))}', ${buildRelationshipMetadata(relationship)})`);
8
8
  return `kb_commit_upsert(${type}, ${buildPropertyList(input.entity)}, [${relationships.join(", ")}], ${input.skipContradictionCheck ? "true" : "false"}, ChangeKind)`;
9
9
  }
10
+ // implements REQ-kibi-operation-interface-parity
11
+ /**
12
+ * Goal that stages the same entity and relationships the commit goal would
13
+ * and runs the commit-time contradiction check inside a transaction that is
14
+ * always rolled back (kb_preview_upsert_contradiction/3). It succeeds when
15
+ * the commit would pass the check and fails with the commit's error when it
16
+ * would not; it never writes.
17
+ */
18
+ export function buildUpsertContradictionPreviewGoal(input) {
19
+ const type = String(input.entity.type);
20
+ const relationships = input.relationships.map((relationship) => `rel(${String(relationship.type)}, '${escapeAtom(String(relationship.from))}', '${escapeAtom(String(relationship.to))}', ${buildRelationshipMetadata(relationship)})`);
21
+ return `kb_preview_upsert_contradiction(${type}, ${buildPropertyList(input.entity)}, [${relationships.join(", ")}])`;
22
+ }
10
23
  function upsertBatchEntry(input) {
11
24
  const type = String(input.entity.type);
12
25
  const relationships = input.relationships.map((relationship) => `rel(${String(relationship.type)}, '${escapeAtom(String(relationship.from))}', '${escapeAtom(String(relationship.to))}', ${buildRelationshipMetadata(relationship)})`);
@@ -8,6 +8,18 @@ export type SourceWriteReceipt = Readonly<{
8
8
  created: boolean;
9
9
  }>;
10
10
  export declare function writePendingSourceReceipt(workspaceRoot: string, relativePath: string, afterHash: string): void;
11
+ type FileTimes = Readonly<{
12
+ atime: Date;
13
+ mtime: Date;
14
+ }>;
15
+ /** The access and modification times of a file, or `null` when unreadable. */
16
+ export declare function readFileTimes(absolutePath: string): FileTimes | null;
17
+ /**
18
+ * Put a restored file's times back so freshness checks, which compare file
19
+ * times with the compiled snapshot, do not report a rolled-back write as a
20
+ * newer source. Best effort: a port without a host file is left alone.
21
+ */
22
+ export declare function restoreFileTimes(absolutePath: string, times: FileTimes | null): void;
11
23
  /**
12
24
  * Retire the pending receipt of a source that a committed plan deleted. A
13
25
  * receipt binds untracked input to exact bytes, so one left behind for a
@@ -74,4 +86,5 @@ export declare function writeSourceForUpsert(input: UpsertInput, entity: Readonl
74
86
  } | null>;
75
87
  /** Resolve and normalize the canonical authored path for an entity. */
76
88
  export declare function canonicalSourcePath(context: OperationContext, input: UpsertInput, entity: Readonly<Record<string, unknown>>, existing?: Readonly<Record<string, unknown>>): string;
89
+ export {};
77
90
  //# sourceMappingURL=source-authoring.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,46 @@ export function writePendingSourceReceipt(workspaceRoot, relativePath, afterHash
25
25
  createdAt: new Date().toISOString(),
26
26
  }, null, 2)}\n`, { mode: 0o600 });
27
27
  }
28
+ /** The access and modification times of a file, or `null` when unreadable. */
29
+ // implements REQ-core-atomic-upsert-persistence
30
+ export function readFileTimes(absolutePath) {
31
+ try {
32
+ const stat = fs.statSync(absolutePath);
33
+ return { atime: stat.atime, mtime: stat.mtime };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Put a restored file's times back so freshness checks, which compare file
41
+ * times with the compiled snapshot, do not report a rolled-back write as a
42
+ * newer source. Best effort: a port without a host file is left alone.
43
+ */
44
+ // implements REQ-core-atomic-upsert-persistence
45
+ export function restoreFileTimes(absolutePath, times) {
46
+ if (times === null)
47
+ return;
48
+ try {
49
+ fs.utimesSync(absolutePath, times.atime, times.mtime);
50
+ }
51
+ catch {
52
+ // The bytes are restored; a failed time reset only costs a sync.
53
+ }
54
+ }
55
+ /**
56
+ * Snapshot a pending-source receipt's bytes, or `null` when none exists, so a
57
+ * rolled-back write can restore the receipt exactly as it found it.
58
+ */
59
+ // implements REQ-core-atomic-upsert-persistence
60
+ function readPendingSourceReceiptBytes(receiptPath) {
61
+ try {
62
+ return fs.readFileSync(receiptPath, "utf8");
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
28
68
  /**
29
69
  * Retire the pending receipt of a source that a committed plan deleted. A
30
70
  * receipt binds untracked input to exact bytes, so one left behind for a
@@ -644,6 +684,10 @@ export async function writeSourceForUpsert(input, entity, existing, context, sou
644
684
  catch {
645
685
  before = undefined;
646
686
  }
687
+ // Freshness compares file times with the compiled snapshot, so restoring
688
+ // the bytes alone would still make an untouched entity read as stale after
689
+ // a rolled-back write. Snapshot the times before the publication below.
690
+ const priorTimes = before === undefined ? null : readFileTimes(absolute);
647
691
  // Proof-receipt ingest supplies pre-patched bytes so unrelated frontmatter
648
692
  // keeps its authored formatting (and the workspace snapshot stays stable).
649
693
  // Only valid when the document already exists; a new document still needs
@@ -676,6 +720,12 @@ export async function writeSourceForUpsert(input, entity, existing, context, sou
676
720
  afterHash: digest(after),
677
721
  created: before === undefined,
678
722
  };
723
+ const pendingReceipt = pendingReceiptPath(context.workspaceRoot, relative);
724
+ // Snapshot the pending-source receipt before this write republishes it. A
725
+ // commit-time refusal restores the authored bytes, so the receipt must go
726
+ // back to the hash it bound before the write (or disappear again) or the
727
+ // next sync reports hash drift against a file that never changed.
728
+ const priorReceiptBytes = readPendingSourceReceiptBytes(pendingReceipt);
679
729
  const rollback = async () => {
680
730
  const fsPort = context.fs;
681
731
  if (fsPort === undefined)
@@ -700,8 +750,14 @@ export async function writeSourceForUpsert(input, entity, existing, context, sou
700
750
  await fsPort.unlink(absolute);
701
751
  else
702
752
  await fsPort.writeFile(absolute, "");
753
+ // implements REQ-core-atomic-upsert-persistence
754
+ // A receipt that predates this write (its file was removed outside
755
+ // Kibi) keeps binding the missing source so sync still reports it.
703
756
  try {
704
- fs.unlinkSync(pendingReceiptPath(context.workspaceRoot, relative));
757
+ if (priorReceiptBytes === null)
758
+ fs.unlinkSync(pendingReceipt);
759
+ else
760
+ fs.writeFileSync(pendingReceipt, priorReceiptBytes, { mode: 0o600 });
705
761
  }
706
762
  catch {
707
763
  // The pending receipt is advisory recovery metadata.
@@ -722,9 +778,21 @@ export async function writeSourceForUpsert(input, entity, existing, context, sou
722
778
  await fsPort.unlink?.(rollbackTemp).catch(() => undefined);
723
779
  throw error;
724
780
  }
781
+ // implements REQ-core-atomic-upsert-persistence
782
+ // The authored bytes are back to `before`; rebind the pending receipt
783
+ // and the file times to the same state they had before this write.
784
+ try {
785
+ if (priorReceiptBytes === null)
786
+ fs.unlinkSync(pendingReceipt);
787
+ else
788
+ fs.writeFileSync(pendingReceipt, priorReceiptBytes, { mode: 0o600 });
789
+ }
790
+ catch {
791
+ // The pending receipt is advisory recovery metadata.
792
+ }
793
+ restoreFileTimes(absolute, priorTimes);
725
794
  }
726
795
  };
727
- const pendingReceipt = pendingReceiptPath(context.workspaceRoot, relative);
728
796
  try {
729
797
  if (receipt.afterHash !== null &&
730
798
  (before === undefined || fs.existsSync(pendingReceipt))) {
@@ -1 +1 @@
1
- {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAUxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AA4BnF,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AA6CpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAmCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC;CACnE,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAgF3B;AAGD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAsgBzC"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAUxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AA8BnF,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AA6CpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC;CACnE,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAgF3B;AAGD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA4gBzC"}
@@ -20,7 +20,7 @@ import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.
20
20
  import { existingRelationships, validateLiveRelationshipTargets, validateRelationshipSources, validateStrictLanePairing, validateSupersedesSourceHistory, } from "./relationships.js";
21
21
  import { withStoredRequirementSemantics } from "./requirement-semantics.js";
22
22
  import { MutationRollbackFailureError, MutationSaga } from "./saga.js";
23
- import { writePendingSourceReceipt, writeSourceForUpsert, } from "./source-authoring.js";
23
+ import { readFileTimes, restoreFileTimes, writePendingSourceReceipt, writeSourceForUpsert, } from "./source-authoring.js";
24
24
  import { storedEntityLookup, } from "./stored-entity.js";
25
25
  import { acquireSymbolCompilerLock, releaseSymbolCompilerLock, } from "./symbol-compiler-lock.js";
26
26
  import { validateSymbolGranularity } from "./symbol-granularity.js";
@@ -69,7 +69,7 @@ export function assertRelationshipShardContained(workspaceRoot, shardPath) {
69
69
  throw new OperationError("SOURCE_PATH_INVALID", `Relationship shard follows a symlink outside the workspace: ${shardPath}`);
70
70
  }
71
71
  }
72
- function restoreRelationshipShard(shardPath, before, expectedAfterHash) {
72
+ function restoreRelationshipShard(shardPath, before, expectedAfterHash, beforeTimes = null) {
73
73
  // Compare before restore: a concurrent writer may have replaced the shard
74
74
  // after our append. Never clobber newer bytes; the next check/sync reports
75
75
  // any canonical source path that needs reconciliation.
@@ -96,6 +96,10 @@ function restoreRelationshipShard(shardPath, before, expectedAfterHash) {
96
96
  return;
97
97
  }
98
98
  writeFileSync(shardPath, before, "utf8");
99
+ // implements REQ-core-atomic-upsert-persistence
100
+ // Freshness compares file times with the compiled snapshot; a restored
101
+ // shard with a new time would read as a newer source after a refusal.
102
+ restoreFileTimes(shardPath, beforeTimes);
99
103
  }
100
104
  export async function validateAppendOnlyProofReceipts(entity, context, staged) {
101
105
  if (entity.type !== "test")
@@ -269,6 +273,7 @@ export async function executeUpsert(input, context, options = {}) {
269
273
  let sourceMutationLock;
270
274
  let operationFailure;
271
275
  const relationshipShardBefore = new Map();
276
+ const relationshipShardBeforeTimes = new Map();
272
277
  const relationshipShardAfterHash = new Map();
273
278
  let planRecoveryWarnings = [];
274
279
  const holdsSymbolCompilerLock = input.type === "symbol" &&
@@ -346,7 +351,7 @@ export async function executeUpsert(input, context, options = {}) {
346
351
  name: "relationship-shards",
347
352
  rollback: () => {
348
353
  for (const [shardPath, before] of relationshipShardBefore) {
349
- restoreRelationshipShard(shardPath, before, relationshipShardAfterHash.get(shardPath) ?? null);
354
+ restoreRelationshipShard(shardPath, before, relationshipShardAfterHash.get(shardPath) ?? null, relationshipShardBeforeTimes.get(shardPath) ?? null);
350
355
  }
351
356
  },
352
357
  });
@@ -360,6 +365,7 @@ export async function executeUpsert(input, context, options = {}) {
360
365
  assertRelationshipShardContained(context.workspaceRoot, shardPath);
361
366
  if (!relationshipShardBefore.has(shardPath)) {
362
367
  relationshipShardBefore.set(shardPath, existsSync(shardPath) ? readFileSync(shardPath, "utf8") : null);
368
+ relationshipShardBeforeTimes.set(shardPath, readFileTimes(shardPath));
363
369
  }
364
370
  appendRelationship(path.join(context.workspaceRoot, ".kb"), {
365
371
  type,
@@ -1 +1 @@
1
- {"version":3,"file":"validate-upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validate-upsert.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAOxE,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA+CrE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CA4DjD"}
1
+ {"version":3,"file":"validate-upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validate-upsert.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAWxE,OAAO,KAAK,EAEV,WAAW,EACX,qBAAqB,EACtB,MAAM,YAAY,CAAC;AA2EpB;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAuEjD"}
@@ -4,6 +4,7 @@ import { entityIdStyleWarnings } from "../../utils/entity-id-style.js";
4
4
  import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
5
5
  import { assertSemanticInventoryBoundary } from "../semantic-advisor/ingestion-boundary.js";
6
6
  import { entityContextWarnings } from "./context-warning.js";
7
+ import { buildUpsertContradictionPreviewGoal, formatUpsertError, } from "./contradictions.js";
7
8
  import { validateRelationshipSources } from "./relationships.js";
8
9
  import { validateSymbolGranularity } from "./symbol-granularity.js";
9
10
  import { validateAppendOnlyProofReceipts, validateUpsertForCommit, } from "./upsert.js";
@@ -13,6 +14,25 @@ async function entityExists(prolog, id) {
13
14
  const result = await prolog.query(`once(kb_entity('${escapeAtom(id)}', _Type, _Props))`);
14
15
  return result.success;
15
16
  }
17
+ // implements REQ-kibi-operation-interface-parity, REQ-kibi-truthful-consistency
18
+ /**
19
+ * The commit-time contradiction check, run against a staged and rolled-back
20
+ * copy of the store. Only a `req` commit runs it (facts and scenarios never
21
+ * do), and `_skipContradictionCheck` skips it at commit, so the preview skips
22
+ * it too. A refusal carries the same message the commit would raise.
23
+ */
24
+ async function previewContradictions(prolog, input, entity, relationships) {
25
+ if (input.type !== "req" || input._skipContradictionCheck === true)
26
+ return;
27
+ const preview = await prolog.query(buildUpsertContradictionPreviewGoal({
28
+ entity,
29
+ relationships,
30
+ skipContradictionCheck: false,
31
+ }));
32
+ if (!preview.success) {
33
+ throw new Error(formatUpsertError(input.id, preview.error, preview.errorRecord));
34
+ }
35
+ }
16
36
  /**
17
37
  * The validation chain for a runtime without a store: everything that does
18
38
  * not need to read the KB.
@@ -39,10 +59,16 @@ async function validateWithoutStore(input, context) {
39
59
  export async function executeValidateUpsert(input, context) {
40
60
  try {
41
61
  const { entity, semantic } = context.prolog !== undefined
42
- ? await validateUpsertForCommit(input, context).then((result) => ({
43
- entity: result.validated.entity,
44
- semantic: result.semantic,
45
- }))
62
+ ? await validateUpsertForCommit(input, context).then(async (result) => {
63
+ // The commit goal checks requirement contradictions after the
64
+ // validations above; a dry run previews that stage as well, so
65
+ // "valid" means the commit would not refuse the write.
66
+ await previewContradictions(context.prolog, input, result.validated.entity, result.validated.relationships);
67
+ return {
68
+ entity: result.validated.entity,
69
+ semantic: result.semantic,
70
+ };
71
+ })
46
72
  : await validateWithoutStore(input, context);
47
73
  // Style warnings apply only to entities this upsert would create, so the
48
74
  // existence read happens only when the ID actually has a style issue.