kibi-cli 2.31.1 → 2.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/cli-register-proof.d.ts.map +1 -1
  2. package/dist/cli-register-proof.js +19 -7
  3. package/dist/cli-register-reporting.d.ts.map +1 -1
  4. package/dist/cli-register-reporting.js +2 -0
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +5 -1
  7. package/dist/commands/check-generated.d.ts +5 -1
  8. package/dist/commands/check-generated.d.ts.map +1 -1
  9. package/dist/commands/check-generated.js +24 -35
  10. package/dist/commands/check.d.ts +1 -0
  11. package/dist/commands/check.d.ts.map +1 -1
  12. package/dist/commands/check.js +14 -5
  13. package/dist/commands/discovery-table.d.ts.map +1 -1
  14. package/dist/commands/discovery-table.js +51 -4
  15. package/dist/commands/doctor.js +19 -4
  16. package/dist/commands/init-helpers.d.ts.map +1 -1
  17. package/dist/commands/init-helpers.js +8 -5
  18. package/dist/commands/migrate.d.ts.map +1 -1
  19. package/dist/commands/migrate.js +109 -5
  20. package/dist/commands/prove.d.ts +48 -1
  21. package/dist/commands/prove.d.ts.map +1 -1
  22. package/dist/commands/prove.js +293 -8
  23. package/dist/commands/report.d.ts +1 -0
  24. package/dist/commands/report.d.ts.map +1 -1
  25. package/dist/commands/report.js +70 -5
  26. package/dist/commands/signoff.d.ts.map +1 -1
  27. package/dist/commands/signoff.js +20 -1
  28. package/dist/commands/sync/manifest.d.ts +10 -1
  29. package/dist/commands/sync/manifest.d.ts.map +1 -1
  30. package/dist/commands/sync/manifest.js +106 -17
  31. package/dist/commands/sync/persistence.d.ts.map +1 -1
  32. package/dist/commands/sync/persistence.js +16 -5
  33. package/dist/commands/sync.d.ts.map +1 -1
  34. package/dist/commands/sync.js +19 -0
  35. package/dist/diagnostics.js +1 -1
  36. package/dist/engine.js +1 -1
  37. package/dist/extractors/markdown.d.ts +4 -2
  38. package/dist/extractors/markdown.d.ts.map +1 -1
  39. package/dist/extractors/markdown.js +17 -0
  40. package/dist/extractors/symbol-coordinates.js +2 -2
  41. package/dist/extractors/symbols-coordinator.d.ts +2 -4
  42. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  43. package/dist/extractors/symbols-coordinator.js +1 -2
  44. package/dist/operations/bootstrap/candidate-helpers.d.ts +3 -0
  45. package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
  46. package/dist/operations/bootstrap/candidate-helpers.js +4 -0
  47. package/dist/operations/migration/evidence-floor.d.ts +41 -0
  48. package/dist/operations/migration/evidence-floor.d.ts.map +1 -0
  49. package/dist/operations/migration/evidence-floor.js +230 -0
  50. package/dist/operations/migration/proof-contract-claims.d.ts +72 -0
  51. package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -0
  52. package/dist/operations/migration/proof-contract-claims.js +215 -0
  53. package/dist/operations/migration/schema6.d.ts +6 -0
  54. package/dist/operations/migration/schema6.d.ts.map +1 -1
  55. package/dist/operations/migration/schema6.js +92 -0
  56. package/dist/operations/migration/symbol-coordinates-refresh.d.ts +3 -2
  57. package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -1
  58. package/dist/operations/migration/symbol-coordinates-refresh.js +4 -3
  59. package/dist/operations/migration/untrack-symbol-coordinates.d.ts +23 -0
  60. package/dist/operations/migration/untrack-symbol-coordinates.d.ts.map +1 -0
  61. package/dist/operations/migration/untrack-symbol-coordinates.js +67 -0
  62. package/dist/operations/modeling/requirement-modeler.d.ts.map +1 -1
  63. package/dist/operations/modeling/requirement-modeler.js +48 -1
  64. package/dist/operations/modeling/requirement-types.d.ts +2 -1
  65. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  66. package/dist/operations/modeling/requirement-utils.d.ts +1 -1
  67. package/dist/operations/modeling/requirement-utils.d.ts.map +1 -1
  68. package/dist/operations/modeling/requirement-utils.js +8 -1
  69. package/dist/operations/mutation/omitted-properties.d.ts +11 -0
  70. package/dist/operations/mutation/omitted-properties.d.ts.map +1 -0
  71. package/dist/operations/mutation/omitted-properties.js +76 -0
  72. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  73. package/dist/operations/mutation/serialization.js +1 -0
  74. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  75. package/dist/operations/mutation/source-authoring.js +39 -20
  76. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  77. package/dist/operations/mutation/strict-fact.js +6 -2
  78. package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
  79. package/dist/operations/mutation/symbol-granularity.js +5 -3
  80. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  81. package/dist/operations/mutation/upsert.js +7 -1
  82. package/dist/operations/mutation/validation.d.ts +2 -0
  83. package/dist/operations/mutation/validation.d.ts.map +1 -1
  84. package/dist/operations/mutation/validation.js +4 -0
  85. package/dist/operations/proof/claim-bindings.d.ts +41 -0
  86. package/dist/operations/proof/claim-bindings.d.ts.map +1 -0
  87. package/dist/operations/proof/claim-bindings.js +364 -0
  88. package/dist/operations/proof/code-certificates.d.ts +155 -0
  89. package/dist/operations/proof/code-certificates.d.ts.map +1 -0
  90. package/dist/operations/proof/code-certificates.js +894 -0
  91. package/dist/operations/proof/code-models.d.ts +96 -0
  92. package/dist/operations/proof/code-models.d.ts.map +1 -0
  93. package/dist/operations/proof/code-models.js +601 -0
  94. package/dist/operations/proof/code-runner.d.ts +40 -0
  95. package/dist/operations/proof/code-runner.d.ts.map +1 -0
  96. package/dist/operations/proof/code-runner.js +164 -0
  97. package/dist/operations/proof/code-scope.d.ts +14 -1
  98. package/dist/operations/proof/code-scope.d.ts.map +1 -1
  99. package/dist/operations/proof/code-scope.js +44 -2
  100. package/dist/operations/proof/compact-receipts.d.ts.map +1 -1
  101. package/dist/operations/proof/compact-receipts.js +4 -3
  102. package/dist/operations/proof/explain.d.ts +32 -0
  103. package/dist/operations/proof/explain.d.ts.map +1 -1
  104. package/dist/operations/proof/explain.js +91 -0
  105. package/dist/operations/proof/ingest-proof.d.ts +9 -1
  106. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  107. package/dist/operations/proof/ingest-proof.js +42 -2
  108. package/dist/operations/proof/receipt-compaction.d.ts +2 -0
  109. package/dist/operations/proof/receipt-compaction.d.ts.map +1 -1
  110. package/dist/operations/proof/receipt-compaction.js +6 -2
  111. package/dist/operations/proof/regular-language.d.ts +89 -0
  112. package/dist/operations/proof/regular-language.d.ts.map +1 -0
  113. package/dist/operations/proof/regular-language.js +943 -0
  114. package/dist/operations/proof/source-code-readers.d.ts +12 -0
  115. package/dist/operations/proof/source-code-readers.d.ts.map +1 -0
  116. package/dist/operations/proof/source-code-readers.js +699 -0
  117. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  118. package/dist/operations/semantic-advisor/analyze-prose.js +2 -1
  119. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  120. package/dist/operations/semantic-advisor/shared.js +4 -0
  121. package/dist/operations/semantic-advisor/strict-count-rules.d.ts.map +1 -1
  122. package/dist/operations/semantic-advisor/strict-count-rules.js +8 -2
  123. package/dist/operations/semantic-advisor/strict-rules.d.ts +8 -0
  124. package/dist/operations/semantic-advisor/strict-rules.d.ts.map +1 -1
  125. package/dist/operations/semantic-advisor/strict-rules.js +234 -4
  126. package/dist/operations/semantic-advisor/types.d.ts +3 -2
  127. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  128. package/dist/plugins/approved-source-analyzers.generated.d.ts +6 -6
  129. package/dist/plugins/approved-source-analyzers.generated.js +6 -6
  130. package/dist/prolog.d.ts.map +1 -1
  131. package/dist/prolog.js +13 -3
  132. package/dist/proof/command-test-report.d.ts +4 -3
  133. package/dist/proof/command-test-report.d.ts.map +1 -1
  134. package/dist/proof/command-test-report.js +4 -10
  135. package/dist/proof/evaluate.d.ts.map +1 -1
  136. package/dist/proof/evaluate.js +18 -2
  137. package/dist/proof/inspect.d.ts.map +1 -1
  138. package/dist/proof/inspect.js +58 -0
  139. package/dist/proof/producers/playwright-reporter.d.ts +3 -0
  140. package/dist/proof/producers/playwright-reporter.d.ts.map +1 -1
  141. package/dist/proof/producers/playwright-reporter.js +25 -2
  142. package/dist/proof/step-runner.d.ts +36 -0
  143. package/dist/proof/step-runner.d.ts.map +1 -0
  144. package/dist/proof/step-runner.js +234 -0
  145. package/dist/public/evidence-levels.d.ts +12 -0
  146. package/dist/public/evidence-levels.d.ts.map +1 -0
  147. package/dist/public/evidence-levels.js +19 -0
  148. package/dist/public/impact/coverage-depth-quality.d.ts +7 -0
  149. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  150. package/dist/public/impact/coverage-depth-quality.js +59 -0
  151. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  152. package/dist/public/impact/full-kb-quality.js +3 -1
  153. package/dist/public/operations/catalog.d.ts +315 -11
  154. package/dist/public/operations/catalog.d.ts.map +1 -1
  155. package/dist/public/operations/check-executor.d.ts +2 -0
  156. package/dist/public/operations/check-executor.d.ts.map +1 -1
  157. package/dist/public/operations/check-executor.js +6 -0
  158. package/dist/public/operations/evidence-floor.d.ts +48 -0
  159. package/dist/public/operations/evidence-floor.d.ts.map +1 -0
  160. package/dist/public/operations/evidence-floor.js +143 -0
  161. package/dist/public/operations/migration-plan.d.ts +19 -5
  162. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  163. package/dist/public/operations/migration-plan.js +22 -5
  164. package/dist/public/operations/node-ports.d.ts.map +1 -1
  165. package/dist/public/operations/node-ports.js +4 -1
  166. package/dist/public/operations/proof-contract-claims.d.ts +26 -0
  167. package/dist/public/operations/proof-contract-claims.d.ts.map +1 -0
  168. package/dist/public/operations/proof-contract-claims.js +71 -0
  169. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  170. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  171. package/dist/public/operations/specs/check-rules.generated.js +3 -1
  172. package/dist/public/operations/specs/check.d.ts +1 -1
  173. package/dist/public/operations/specs/composite.d.ts +16 -3
  174. package/dist/public/operations/specs/composite.d.ts.map +1 -1
  175. package/dist/public/operations/specs/modeling.d.ts +16 -3
  176. package/dist/public/operations/specs/modeling.d.ts.map +1 -1
  177. package/dist/public/operations/specs/modeling.js +22 -3
  178. package/dist/public/operations/specs/mutation-schemas.d.ts +289 -7
  179. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  180. package/dist/public/operations/specs/mutation-schemas.js +28 -4
  181. package/dist/public/operations/specs/proof.d.ts +9 -0
  182. package/dist/public/operations/specs/proof.d.ts.map +1 -1
  183. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  184. package/dist/public/operations/specs/reporting.js +19 -10
  185. package/dist/public/operations/specs/upsert.d.ts +289 -7
  186. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  187. package/dist/public/proof-certificates.d.ts +163 -0
  188. package/dist/public/proof-certificates.d.ts.map +1 -0
  189. package/dist/public/proof-certificates.js +419 -0
  190. package/dist/public/proof-protocol.d.ts +259 -3
  191. package/dist/public/proof-protocol.d.ts.map +1 -1
  192. package/dist/public/proof-protocol.js +524 -3
  193. package/dist/public/proof-receipt.d.ts +90 -6
  194. package/dist/public/proof-receipt.d.ts.map +1 -1
  195. package/dist/public/proof-receipt.js +88 -7
  196. package/dist/public/proof-sources.d.ts +102 -0
  197. package/dist/public/proof-sources.d.ts.map +1 -0
  198. package/dist/public/proof-sources.js +1292 -0
  199. package/dist/public/schemas/entity.d.ts.map +1 -1
  200. package/dist/public/schemas/entity.js +39 -2
  201. package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
  202. package/dist/public/skills/kibi-commit-review/SKILL.md +8 -8
  203. package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
  204. package/dist/public/skills/kibi-decisions/SKILL.md +13 -2
  205. package/dist/public/skills/kibi-decisions/resources/decision-records.md +9 -1
  206. package/dist/public/skills/kibi-freshness/SKILL.md +5 -1
  207. package/dist/public/skills/kibi-traceability/SKILL.md +12 -1
  208. package/dist/public/skills/kibi-usage/SKILL.md +8 -4
  209. package/dist/public/skills/kibi-usage/resources/proof.md +221 -20
  210. package/dist/public/skills/kibi-usage/resources/workflows.md +4 -4
  211. package/dist/report/html-report.css +83 -11
  212. package/dist/report/html-report.d.ts +8 -0
  213. package/dist/report/html-report.d.ts.map +1 -1
  214. package/dist/report/html-report.js +214 -22
  215. package/dist/report/report-view.d.ts +2 -2
  216. package/dist/report/report-view.d.ts.map +1 -1
  217. package/dist/report/report-view.js +8 -0
  218. package/dist/schemas/entity.schema.json +404 -5
  219. package/dist/search-answer.d.ts.map +1 -1
  220. package/dist/search-answer.js +1 -0
  221. package/dist/signoff/html.d.ts.map +1 -1
  222. package/dist/signoff/html.js +3 -0
  223. package/dist/signoff/markdown.d.ts.map +1 -1
  224. package/dist/signoff/markdown.js +3 -0
  225. package/dist/signoff/model.d.ts +20 -0
  226. package/dist/signoff/model.d.ts.map +1 -1
  227. package/dist/signoff/model.js +20 -1
  228. package/dist/traceability/evidence-model.d.ts +13 -12
  229. package/dist/traceability/evidence-model.d.ts.map +1 -1
  230. package/dist/traceability/evidence-model.js +1 -1
  231. package/dist/traceability/impact-knowledge.d.ts.map +1 -1
  232. package/dist/traceability/impact-knowledge.js +1 -0
  233. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  234. package/dist/traceability/staged-diagnostics.js +24 -20
  235. package/dist/traceability/staged-impact-contract.d.ts +4 -4
  236. package/dist/traceability/staged-impact-contract.js +9 -9
  237. package/dist/traceability/staged-symbols-manifest.d.ts +11 -3
  238. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  239. package/dist/traceability/staged-symbols-manifest.js +47 -140
  240. package/dist/traceability/temp-kb.d.ts.map +1 -1
  241. package/dist/traceability/temp-kb.js +1 -0
  242. package/dist/types/entities.d.ts +5 -2
  243. package/dist/types/entities.d.ts.map +1 -1
  244. package/dist/utils/merge-driver-setup.d.ts +8 -1
  245. package/dist/utils/merge-driver-setup.d.ts.map +1 -1
  246. package/dist/utils/merge-driver-setup.js +23 -2
  247. package/dist/utils/rule-registry.generated.d.ts +10 -0
  248. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  249. package/dist/utils/rule-registry.generated.js +12 -0
  250. package/dist/utils/schema-version.d.ts +1 -1
  251. package/dist/utils/schema-version.d.ts.map +1 -1
  252. package/dist/utils/schema-version.js +7 -1
  253. package/dist/utils/strict-modeling.d.ts +3 -2
  254. package/dist/utils/strict-modeling.d.ts.map +1 -1
  255. package/dist/utils/strict-modeling.js +37 -4
  256. package/dist/workspace-cwd.d.ts +15 -0
  257. package/dist/workspace-cwd.d.ts.map +1 -0
  258. package/dist/workspace-cwd.js +86 -0
  259. package/package.json +6 -2
  260. package/schema/entities.pl +6 -0
  261. package/schema/validation.pl +7 -2
  262. package/src/public/evidence-levels.ts +23 -0
  263. package/src/public/impact/coverage-depth-quality.ts +73 -0
  264. package/src/public/impact/full-kb-quality.ts +6 -1
  265. package/src/public/operations/check-executor.ts +13 -0
  266. package/src/public/operations/evidence-floor.ts +203 -0
  267. package/src/public/operations/migration-plan.ts +39 -6
  268. package/src/public/operations/node-ports.ts +3 -1
  269. package/src/public/operations/proof-contract-claims.ts +103 -0
  270. package/src/public/operations/specs/check-rules.generated.ts +3 -1
  271. package/src/public/operations/specs/modeling.ts +24 -5
  272. package/src/public/operations/specs/mutation-schemas.ts +30 -4
  273. package/src/public/operations/specs/reporting.ts +20 -10
  274. package/src/public/proof-certificates.ts +566 -0
  275. package/src/public/proof-protocol-types.d.ts +69 -1
  276. package/src/public/proof-protocol.ts +694 -3
  277. package/src/public/proof-receipt.ts +133 -7
  278. package/src/public/proof-sources.ts +1656 -0
  279. package/src/public/schemas/entity.ts +51 -3
  280. package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
  281. package/src/public/skills/kibi-commit-review/SKILL.md +8 -8
  282. package/src/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
  283. package/src/public/skills/kibi-decisions/SKILL.md +13 -2
  284. package/src/public/skills/kibi-decisions/resources/decision-records.md +9 -1
  285. package/src/public/skills/kibi-freshness/SKILL.md +5 -1
  286. package/src/public/skills/kibi-traceability/SKILL.md +12 -1
  287. package/src/public/skills/kibi-usage/SKILL.md +8 -4
  288. package/src/public/skills/kibi-usage/resources/proof.md +221 -20
  289. package/src/public/skills/kibi-usage/resources/workflows.md +4 -4
  290. package/src/schemas/entity.schema.json +404 -5
  291. package/dist/commands/proof-impact.d.ts +0 -7
  292. package/dist/commands/proof-impact.d.ts.map +0 -1
  293. package/dist/commands/proof-impact.js +0 -39
  294. package/dist/operations/proof/baseline-diff.d.ts +0 -41
  295. package/dist/operations/proof/baseline-diff.d.ts.map +0 -1
  296. package/dist/operations/proof/baseline-diff.js +0 -153
  297. package/dist/operations/proof/impact.d.ts +0 -51
  298. package/dist/operations/proof/impact.d.ts.map +0 -1
  299. package/dist/operations/proof/impact.js +0 -138
  300. package/dist/traceability/reviewed-coordinate-refresh.d.ts +0 -10
  301. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +0 -1
  302. package/dist/traceability/reviewed-coordinate-refresh.js +0 -49
@@ -0,0 +1,203 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import { readKbManifest } from "../../utils/kb-manifest.js";
20
+ import type { Violation } from "../../utils/rule-registry.js";
21
+ import { PROOF_RECEIPT_MAX_AGE_SECONDS } from "../proof-receipt.js";
22
+ import { runOperationJsonQuery, toPrologAtom } from "./prolog-json.js";
23
+ import type { OperationContext } from "./runtime-types.js";
24
+ import { readWorkspaceSnapshot } from "./workspace-snapshot.js";
25
+
26
+ /**
27
+ * The schema version from which a current requirement's evidence level must
28
+ * reach its floor; older KBs get the floor with their schema upgrade, whose
29
+ * migration records the floor of firm requirements without a proof path.
30
+ */
31
+ // implements REQ-kibi-evidence-floors
32
+ export const EVIDENCE_FLOOR_SCHEMA_VERSION = 11;
33
+
34
+ /** A current requirement's evidence level must reach its floor. */
35
+ // implements REQ-kibi-evidence-floors
36
+ export const EVIDENCE_FLOOR_RULE = "evidence-floor";
37
+
38
+ // implements REQ-kibi-evidence-floors
39
+ export type EvidenceFloorRow = Readonly<{
40
+ id: string;
41
+ evidenceLevel: string;
42
+ requiredEvidence: string;
43
+ requiredEvidenceSource: string;
44
+ status: "below" | "pending_reproof";
45
+ proofStatus: string;
46
+ proofGaps: readonly string[];
47
+ /** The tests and symbols behind the gaps; empty lists are left out. */
48
+ gapDetails?: Readonly<Record<string, readonly string[]>>;
49
+ }>;
50
+
51
+ type EvidenceFloorPayload = {
52
+ readonly evaluated?: number;
53
+ readonly rows?: readonly EvidenceFloorRow[];
54
+ };
55
+
56
+ export type EvidenceFloorOptions = Readonly<{
57
+ /**
58
+ * Judge receipts as they are: stale receipts no longer count as pending
59
+ * re-proof, and the proof snapshot must be clean. CI uses it right after
60
+ * `kibi prove --all`.
61
+ */
62
+ freshEvidence?: boolean;
63
+ }>;
64
+
65
+ function floorSource(row: EvidenceFloorRow): string {
66
+ return row.requiredEvidenceSource === "explicit"
67
+ ? "its required_evidence"
68
+ : row.requiredEvidence === "tested"
69
+ ? "the floor of a firm requirement"
70
+ : "its floor";
71
+ }
72
+
73
+ const GAP_DETAIL_LABELS: Readonly<Record<string, string>> = {
74
+ failedReceiptTests: "failing receipts",
75
+ missingReceiptTests: "no receipt",
76
+ staleReceiptTests: "stale receipts",
77
+ invalidReceiptTests: "invalid receipts",
78
+ contractMismatchReceiptTests: "receipts for an older contract",
79
+ uncoveredSymbols: "production symbols without qualifying coverage",
80
+ missingCoordinateSymbols: "symbols without source coordinates",
81
+ unprovedClaims: "claims no certificate derives",
82
+ };
83
+
84
+ /** The entities behind the gaps, for a person reading kibi check's text. */
85
+ // implements REQ-kibi-evidence-floors
86
+ export function gapDetailLines(row: EvidenceFloorRow): string[] {
87
+ return Object.entries(row.gapDetails ?? {}).flatMap(([key, ids]) =>
88
+ ids.length === 0
89
+ ? []
90
+ : [
91
+ `${GAP_DETAIL_LABELS[key] ?? key}: ${ids.slice(0, 8).join(", ")}${ids.length > 8 ? ` (+${ids.length - 8} more)` : ""}`,
92
+ ],
93
+ );
94
+ }
95
+
96
+ function suggestionFor(row: EvidenceFloorRow): string {
97
+ if (row.status === "pending_reproof")
98
+ return `Run kibi prove for the tests behind ${row.id} so its receipts match the current code, then rerun kibi check --fresh-evidence.`;
99
+ switch (row.requiredEvidence) {
100
+ case "proved":
101
+ return `Prove every applicable claim of ${row.id}${(row.gapDetails?.unprovedClaims ?? []).length > 0 ? ` (unproved: ${(row.gapDetails?.unprovedClaims ?? []).join(", ")})` : ""}: model each as a set fact (prove it with exhaustive or typecheck), a numeric fact (arithmetic or smt), a rule, predicate or set fact about a predicate (rule or state_machine) or a string fact (regex), name it in a certificate test's proof_contract.proves, and run kibi prove; kibi proof explain ${row.id} shows each claim's bindings. A claim with no such fact can only be attested, so lower required_evidence to tested with the reason in the requirement if that is enough.`;
102
+ case "tested":
103
+ return `Link ${row.id} to a scenario with an end-to-end test that has a proof contract, run kibi prove, and fix its proof gaps (${row.proofGaps.join(", ") || "none listed"}${gapDetailLines(row).length > 0 ? `; ${gapDetailLines(row).join("; ")}` : ""}); kibi proof explain ${row.id} shows each one. Or lower required_evidence if a person decides it does not need tests.`;
104
+ default:
105
+ return `Have a person approve ${row.id} (origin.approved_by) or state it as firm with origin.ref to their words, or lower required_evidence.`;
106
+ }
107
+ }
108
+
109
+ /** One finding per current requirement whose evidence is below its floor. */
110
+ // implements REQ-kibi-evidence-floors
111
+ export function evidenceFloorViolations(
112
+ rows: readonly EvidenceFloorRow[],
113
+ options: EvidenceFloorOptions = {},
114
+ ): Violation[] {
115
+ return rows
116
+ .filter((row) => options.freshEvidence === true || row.status === "below")
117
+ .map((row) => ({
118
+ rule: EVIDENCE_FLOOR_RULE,
119
+ entityId: row.id,
120
+ description:
121
+ row.status === "pending_reproof"
122
+ ? `${row.id} is ${row.evidenceLevel} but needs ${row.requiredEvidence} (${floorSource(row)}); its receipts are older than the code`
123
+ : `${row.id} is ${row.evidenceLevel} but needs ${row.requiredEvidence} (${floorSource(row)})`,
124
+ suggestion: suggestionFor(row),
125
+ evidence: {
126
+ evidenceLevel: row.evidenceLevel,
127
+ requiredEvidence: row.requiredEvidence,
128
+ requiredEvidenceSource: row.requiredEvidenceSource,
129
+ status: row.status,
130
+ proofStatus: row.proofStatus,
131
+ proofGaps: row.proofGaps,
132
+ ...(row.gapDetails === undefined ? {} : { gapDetails: row.gapDetails }),
133
+ },
134
+ }));
135
+ }
136
+
137
+ /**
138
+ * Every current requirement whose evidence is below its floor (status below
139
+ * or pending_reproof), judged on the attached store with the same
140
+ * per-contract receipt binding kb_coverage uses. Unlike the check rule this
141
+ * ignores the schema version, so a migration can read the floor the ladder
142
+ * will apply before it records the new schema.
143
+ */
144
+ // implements REQ-kibi-evidence-floors
145
+ export async function readEvidenceFloorRows(
146
+ context: OperationContext,
147
+ snapshotEvidence?: Awaited<ReturnType<typeof readWorkspaceSnapshot>>,
148
+ ): Promise<readonly EvidenceFloorRow[]> {
149
+ const prolog = context.prolog ?? (await context.ensureProlog?.());
150
+ if (prolog === undefined) return [];
151
+ const evidence = snapshotEvidence ?? (await readWorkspaceSnapshot(context));
152
+ const snapshot = evidence.available ? evidence.snapshot.hash : "unknown";
153
+ const { perContractTestBindings } = await import("./specs/reporting.js");
154
+ const bindings = await perContractTestBindings({ ...context, prolog });
155
+ const checkedAt = context.clock().toISOString();
156
+ const goal = `discovery:evidence_floor_json(${bindings === null ? "strict_snapshot, _{}" : `per_contract, ${bindings}`}, ${toPrologAtom(snapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`;
157
+ const payload = await runOperationJsonQuery<EvidenceFloorPayload>(
158
+ prolog,
159
+ "discovery.pl",
160
+ goal,
161
+ "Evidence floor evaluation",
162
+ );
163
+ return payload.rows ?? [];
164
+ }
165
+
166
+ /**
167
+ * Evaluate the evidence-floor rule against the attached store, with the same
168
+ * per-contract receipt binding kb_coverage uses.
169
+ */
170
+ // implements REQ-kibi-evidence-floors
171
+ export async function collectEvidenceFloorViolations(
172
+ context: OperationContext,
173
+ rules: ReadonlySet<string>,
174
+ options: EvidenceFloorOptions = {},
175
+ ): Promise<Violation[]> {
176
+ if (!rules.has(EVIDENCE_FLOOR_RULE) || context.prolog === undefined)
177
+ return [];
178
+ // A store without a manifest predates schema 11 as much as an old one.
179
+ const schemaVersion = readKbManifest(context.workspaceRoot)?.schemaVersion;
180
+ if (
181
+ schemaVersion === undefined ||
182
+ schemaVersion < EVIDENCE_FLOOR_SCHEMA_VERSION
183
+ )
184
+ return [];
185
+ const snapshotEvidence = await readWorkspaceSnapshot(context);
186
+ const rows = await readEvidenceFloorRows(context, snapshotEvidence);
187
+ const violations = evidenceFloorViolations(rows, options);
188
+ if (options.freshEvidence === true) {
189
+ const clean =
190
+ snapshotEvidence.available && snapshotEvidence.snapshot.dirty === false;
191
+ if (!clean)
192
+ violations.unshift({
193
+ rule: EVIDENCE_FLOOR_RULE,
194
+ entityId: "proof-snapshot",
195
+ description: snapshotEvidence.available
196
+ ? "--fresh-evidence needs a clean proof snapshot, but the working tree has uncommitted changes"
197
+ : `--fresh-evidence needs a proof snapshot, but none is available: ${snapshotEvidence.error}`,
198
+ suggestion:
199
+ "Commit or stash working-tree changes (or run inside a Git checkout), run kibi prove, then rerun kibi check --fresh-evidence.",
200
+ });
201
+ }
202
+ return violations;
203
+ }
@@ -297,8 +297,11 @@ export function mergeMigrationPlans(
297
297
  });
298
298
  }
299
299
 
300
+ /** How many automatic actions a plan digest lists by id. */
301
+ const DIGEST_AUTOMATIC_ACTIONS = 20;
302
+
300
303
  export const MIGRATION_PLAN_DIGEST_VERSION =
301
- "kibi.migration-plan-digest.v1" as const;
304
+ "kibi.migration-plan-digest.v2" as const;
302
305
 
303
306
  export type MigrationPlanDigest = Readonly<{
304
307
  version: typeof MIGRATION_PLAN_DIGEST_VERSION;
@@ -307,7 +310,20 @@ export type MigrationPlanDigest = Readonly<{
307
310
  expected: MigrationPlan["expected"];
308
311
  scope: MigrationPlan["scope"];
309
312
  summary: MigrationPlan["summary"];
310
- automaticActions: readonly MigrationAction[];
313
+ /** The first automatic actions, by id; automaticSummary counts them all. */
314
+ automaticActions: readonly Readonly<{
315
+ id: string;
316
+ code: string;
317
+ state: MigrationActionState;
318
+ autoApplicable: boolean;
319
+ dependsOn: readonly string[];
320
+ }>[];
321
+ automaticSummary: Readonly<{
322
+ total: number;
323
+ ready: number;
324
+ blocked: number;
325
+ omitted: number;
326
+ }>;
311
327
  actionGroups: readonly Readonly<{
312
328
  code: string;
313
329
  safety: MigrationSafety;
@@ -320,9 +336,10 @@ export type MigrationPlanDigest = Readonly<{
320
336
 
321
337
  /**
322
338
  * A bounded summary of a plan for responses that report what is left rather
323
- * than hand over a plan to apply: automatic actions stay whole, every other
324
- * action is counted by code. The full plan comes from status, check and
325
- * coverage.
339
+ * than hand over a plan to apply: the first automatic actions are listed by
340
+ * id and all of them counted, every other action is counted by code. The full
341
+ * plan comes from status, check and coverage, so a response stays small even
342
+ * when hundreds of actions remain.
326
343
  */
327
344
  // implements REQ-agent-guided-migration-orchestration
328
345
  export function digestMigrationPlan(plan: MigrationPlan): MigrationPlanDigest {
@@ -365,7 +382,23 @@ export function digestMigrationPlan(plan: MigrationPlan): MigrationPlanDigest {
365
382
  expected: plan.expected,
366
383
  scope: plan.scope,
367
384
  summary: plan.summary,
368
- automaticActions,
385
+ automaticActions: automaticActions
386
+ .slice(0, DIGEST_AUTOMATIC_ACTIONS)
387
+ .map((action) => ({
388
+ id: action.id,
389
+ code: action.code,
390
+ state: action.state,
391
+ autoApplicable: action.autoApplicable,
392
+ dependsOn: action.dependsOn,
393
+ })),
394
+ automaticSummary: {
395
+ total: automaticActions.length,
396
+ ready: automaticActions.filter(
397
+ (action) => action.state === "ready" && action.autoApplicable,
398
+ ).length,
399
+ blocked: blockedAutomatic,
400
+ omitted: Math.max(0, automaticActions.length - DIGEST_AUTOMATIC_ACTIONS),
401
+ },
369
402
  actionGroups: [...groups.values()].sort(
370
403
  (left, right) =>
371
404
  right.count - left.count ||
@@ -79,7 +79,9 @@ function isReceiptOnlyMarkdownChange(
79
79
  const committed = execFileSync(
80
80
  "git",
81
81
  ["-C", workspaceRoot, "show", `HEAD:${relativePath}`],
82
- { encoding: "buffer", maxBuffer: 64 * 1024 * 1024 },
82
+ // A file staged but not committed yet has no HEAD blob; git's error is
83
+ // expected here and must not reach the terminal.
84
+ { encoding: "buffer", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
83
85
  ) as Buffer;
84
86
  return working.equals(snapshotFileContent(relativePath, committed));
85
87
  } catch {
@@ -0,0 +1,103 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import { readKbManifest } from "../../utils/kb-manifest.js";
20
+ import type { Violation } from "../../utils/rule-registry.js";
21
+ import { runOperationJsonQuery } from "./prolog-json.js";
22
+ import type { OperationContext } from "./runtime-types.js";
23
+
24
+ /**
25
+ * The schema version from which every certificate-kind proof contract must
26
+ * name the claims it proves; older KBs get the rule with their schema
27
+ * upgrade, whose migration pins proves where the claim is unambiguous.
28
+ */
29
+ // implements REQ-kibi-claim-bound-proofs
30
+ export const CLAIM_BINDING_SCHEMA_VERSION = 12;
31
+
32
+ /** A certificate-kind proof contract must name provable claims in proves. */
33
+ // implements REQ-kibi-claim-bound-proofs
34
+ export const PROOF_CONTRACT_CLAIMS_RULE = "proof-contract-claims";
35
+
36
+ // implements REQ-kibi-claim-bound-proofs
37
+ export type ProofContractClaimIssue = Readonly<{
38
+ test_id: string;
39
+ claim_key?: string;
40
+ reason: string;
41
+ }>;
42
+
43
+ /**
44
+ * Every certificate-kind proof contract that names no claims, and every
45
+ * named claim no certificate of its kind could prove. Ignores the schema
46
+ * version, so a migration can read what the rule will report.
47
+ */
48
+ // implements REQ-kibi-claim-bound-proofs
49
+ export async function readProofContractClaimIssues(
50
+ context: OperationContext,
51
+ ): Promise<readonly ProofContractClaimIssue[]> {
52
+ const prolog = context.prolog ?? (await context.ensureProlog?.());
53
+ if (prolog === undefined) return [];
54
+ return runOperationJsonQuery<ProofContractClaimIssue[]>(
55
+ prolog,
56
+ "claim_obligations.pl",
57
+ "claim_obligations:proof_contract_claim_issues_json(JsonString)",
58
+ "Proof contract claims",
59
+ );
60
+ }
61
+
62
+ /** One finding per contract without proves and per unprovable claim key. */
63
+ // implements REQ-kibi-claim-bound-proofs
64
+ export function proofContractClaimViolations(
65
+ issues: readonly ProofContractClaimIssue[],
66
+ ): Violation[] {
67
+ return issues.map((issue) => ({
68
+ rule: PROOF_CONTRACT_CLAIMS_RULE,
69
+ entityId: issue.test_id,
70
+ description:
71
+ issue.claim_key === undefined
72
+ ? `${issue.test_id}: ${issue.reason}`
73
+ : `${issue.test_id} proves ${issue.claim_key}: ${issue.reason}`,
74
+ suggestion:
75
+ issue.claim_key === undefined
76
+ ? `Add proof_contract.proves to ${issue.test_id} with the claim keys (CLAIM-...) of the requirement its certificate establishes; kibi proof explain on the requirement lists its claims.`
77
+ : `Name a modeled claim of a requirement ${issue.test_id} reaches through its scenarios, grounded by a fact this proof kind can express (a set fact for exhaustive or typecheck, a numeric fact for arithmetic or smt, a rule, predicate or set fact for rule or state_machine, a string fact for regex), or remove ${issue.claim_key} from proves.`,
78
+ evidence: {
79
+ testId: issue.test_id,
80
+ ...(issue.claim_key === undefined ? {} : { claimKey: issue.claim_key }),
81
+ reason: issue.reason,
82
+ },
83
+ }));
84
+ }
85
+
86
+ /** Evaluate the proof-contract-claims rule from schema 12. */
87
+ // implements REQ-kibi-claim-bound-proofs
88
+ export async function collectProofContractClaimViolations(
89
+ context: OperationContext,
90
+ rules: ReadonlySet<string>,
91
+ ): Promise<Violation[]> {
92
+ if (!rules.has(PROOF_CONTRACT_CLAIMS_RULE) || context.prolog === undefined)
93
+ return [];
94
+ const schemaVersion = readKbManifest(context.workspaceRoot)?.schemaVersion;
95
+ if (
96
+ schemaVersion === undefined ||
97
+ schemaVersion < CLAIM_BINDING_SCHEMA_VERSION
98
+ )
99
+ return [];
100
+ return proofContractClaimViolations(
101
+ await readProofContractClaimIssues(context),
102
+ );
103
+ }
@@ -65,9 +65,11 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
65
65
  "policy-markers",
66
66
  "kb-consistency",
67
67
  "kb-consistency-unresolved",
68
+ "evidence-floor",
69
+ "proof-contract-claims",
68
70
  ];
69
71
 
70
72
  // implements REQ-006
71
73
  /** Comma-separated "Allowed:" fragment for the kb_check rules description. */
72
74
  export const SELECTABLE_RULES_ALLOWED =
73
- "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers, kb-consistency, kb-consistency-unresolved";
75
+ "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers, kb-consistency, kb-consistency-unresolved, evidence-floor, proof-contract-claims";
@@ -55,13 +55,32 @@ export const modelRequirementSpec = {
55
55
  },
56
56
  operator: {
57
57
  type: "string",
58
- enum: ["eq", "gt", "gte", "lt", "lte", "neq", "bool", "polarity"],
59
- description:
60
- "Optional extracted semantic claim operator. Use gt/lt for strict bounds ('greater than zero' is gt 0, not gte 0). Example: 'eq'.",
58
+ enum: [
59
+ "eq",
60
+ "gt",
61
+ "gte",
62
+ "lt",
63
+ "lte",
64
+ "neq",
65
+ "bool",
66
+ "polarity",
67
+ "matches",
68
+ "includes",
69
+ "subset_of",
70
+ "excludes",
71
+ ],
72
+ description:
73
+ "Optional extracted semantic claim operator. Use gt/lt for strict bounds ('greater than zero' is gt 0, not gte 0). A set claim takes a list value with eq (exactly these), includes (at least these), subset_of (only these) or excludes (none of these); matches takes a regular expression string. Example: 'eq'.",
61
74
  },
62
75
  value: {
63
- description:
64
- "Optional extracted semantic claim value. Accepts string, number, or boolean.",
76
+ anyOf: [
77
+ { type: "string" },
78
+ { type: "number" },
79
+ { type: "boolean" },
80
+ { type: "array", items: { type: "string" }, minItems: 1 },
81
+ ],
82
+ description:
83
+ "Optional extracted semantic claim value: a string, number or boolean, or a list of strings for a set claim. Example: ['requireAuth'] with operator includes.",
65
84
  },
66
85
  provenance: {
67
86
  type: "string",
@@ -1,4 +1,5 @@
1
1
  import { ENTITY_ORIGIN_SCHEMA } from "../../entity-origin.js";
2
+ import { EVIDENCE_LEVELS } from "../../evidence-levels.js";
2
3
  import {
3
4
  PROOF_BINDINGS_SCHEMA,
4
5
  PROOF_CONTRACT_SCHEMA,
@@ -131,6 +132,12 @@ export const ENTITY_PROPERTIES_SCHEMA = {
131
132
  description:
132
133
  "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.",
133
134
  },
135
+ required_evidence: {
136
+ type: "string",
137
+ enum: [...EVIDENCE_LEVELS],
138
+ description:
139
+ "Requirement-only. Weakest evidence level it accepts; the evidence-floor rule fails below it. Absent = tested if firm, else unverified.",
140
+ },
134
141
  rationale: {
135
142
  type: "string",
136
143
  minLength: 1,
@@ -237,15 +244,26 @@ export const ENTITY_PROPERTIES_SCHEMA = {
237
244
  },
238
245
  operator: {
239
246
  type: "string",
240
- enum: ["eq", "neq", "lt", "lte", "gt", "gte"],
247
+ enum: [
248
+ "eq",
249
+ "neq",
250
+ "lt",
251
+ "lte",
252
+ "gt",
253
+ "gte",
254
+ "matches",
255
+ "includes",
256
+ "subset_of",
257
+ "excludes",
258
+ ],
241
259
  description:
242
- "Optional comparison operator for property_value facts. Example: 'eq'.",
260
+ "Optional comparison operator for property_value facts. Example: 'eq'. 'matches' (string facts only): value_string is a JavaScript regular expression (u flag) every value must match as a whole. Set facts only: 'includes' means every member of value_set has the property and other members may have it too; 'subset_of' means every member that has the property is in value_set ('every env var the server reads is one of ...'); 'excludes' means no member of value_set has it ('never imports md5'); 'eq' on a set means exactly those members.",
243
261
  },
244
262
  value_type: {
245
263
  type: "string",
246
- enum: ["string", "int", "number", "bool"],
264
+ enum: ["string", "int", "number", "bool", "set"],
247
265
  description:
248
- "Optional typed value discriminator for property_value facts. Pair with exactly one value_string, value_int, value_number, or value_bool; do not use generic value.",
266
+ "Optional typed value discriminator for property_value facts. Pair with exactly one value_string, value_int, value_number, value_bool, or value_set (set: a list of members, with operator eq, includes, subset_of or excludes); do not use generic value.",
249
267
  },
250
268
  value_string: {
251
269
  type: "string",
@@ -263,6 +281,14 @@ export const ENTITY_PROPERTIES_SCHEMA = {
263
281
  type: "boolean",
264
282
  description: "Optional boolean value for property_value facts.",
265
283
  },
284
+ value_set: {
285
+ type: "array",
286
+ items: { type: "string", minLength: 1 },
287
+ minItems: 1,
288
+ uniqueItems: true,
289
+ description:
290
+ "Optional exact set of values for property_value facts with value_type set, such as the members a domain must have.",
291
+ },
266
292
  unit: {
267
293
  type: "string",
268
294
  description: "Optional unit for numeric property_value facts.",
@@ -242,7 +242,7 @@ export async function perContractTestBindings(
242
242
  ): Promise<string | null> {
243
243
  if (currentProofBindingMode() !== "per_contract") return null;
244
244
  const { loadEntities } = await import("../discovery-entities.js");
245
- const { currentReceiptBindingHash, loadCoveredBySymbolsByTest } =
245
+ const { currentReceiptBindingHashes, loadCoveredBySymbolsByTest } =
246
246
  await import("../../../operations/proof/code-scope.js");
247
247
  // Only the contract, scoped code, and authored source feed the binding
248
248
  // hash. The paged projection never materializes receipt histories, which
@@ -269,14 +269,19 @@ export async function perContractTestBindings(
269
269
  if (testId === "" || fs === undefined) continue;
270
270
  // The same computation ingest uses to write binding_hash. Unreadable or
271
271
  // non-markdown documents keep strict snapshot semantics for that test.
272
- const binding = await currentReceiptBindingHash({
272
+ const bindings = await currentReceiptBindingHashes({
273
273
  workspaceRoot: context.workspaceRoot,
274
274
  readFile: (absolute) => fs.readFile(absolute),
275
275
  test,
276
276
  coveredBySymbols: coveredBy.get(testId) ?? [],
277
277
  });
278
- if (binding !== undefined) {
279
- entries.push(`${toPrologAtom(testId)}: ${toPrologAtom(binding)}`);
278
+ if (bindings !== undefined) {
279
+ // One hash, or the current and the earlier binding a receipt may carry.
280
+ const value =
281
+ bindings.length === 1
282
+ ? toPrologAtom(bindings[0] as string)
283
+ : `[${bindings.map(toPrologAtom).join(", ")}]`;
284
+ entries.push(`${toPrologAtom(testId)}: ${value}`);
280
285
  }
281
286
  }
282
287
  if (entries.length === 0) return null;
@@ -332,16 +337,21 @@ export async function executeCoverage(
332
337
  const statuses = (input.statuses ?? []).map((status) =>
333
338
  validateCoverageStatus(status),
334
339
  );
340
+ // A status filter judges receipts with the same per-contract binding as
341
+ // the unfiltered report; otherwise its rows and summary disagree with
342
+ // every other proof view.
335
343
  const bindingsDict =
336
- statuses.length === 0 && (input.by ?? "req") === "req"
344
+ (input.by ?? "req") === "req"
337
345
  ? await perContractTestBindings(context)
338
346
  : null;
339
347
  const goalFor = (limit: number, offset: number): string =>
340
- bindingsDict !== null
341
- ? `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, per_contract, ${bindingsDict}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`
342
- : statuses.length > 0
343
- ? `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, ${toPrologList(statuses)}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`
344
- : `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`;
348
+ bindingsDict !== null && statuses.length > 0
349
+ ? `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, ${toPrologList(statuses)}, per_contract, ${bindingsDict}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`
350
+ : bindingsDict !== null
351
+ ? `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, per_contract, ${bindingsDict}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`
352
+ : statuses.length > 0
353
+ ? `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, ${toPrologList(statuses)}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`
354
+ : `discovery:coverage_report_json('${input.by ?? "req"}', ${toPrologList(input.tags)}, ${input.includePassing ?? false}, ${input.includeTransitive ?? true}, ${limit}, ${offset}, ${toPrologAtom(codeSnapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`;
345
355
  const payload = await readCoveragePages(
346
356
  requireProlog(context),
347
357
  goalFor,