@descryy/core 0.1.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 (366) hide show
  1. package/LICENSE +21 -0
  2. package/dist/capabilities/index.d.ts +3 -0
  3. package/dist/capabilities/index.d.ts.map +1 -0
  4. package/dist/capabilities/index.js +2 -0
  5. package/dist/capabilities/index.js.map +1 -0
  6. package/dist/capabilities/preconditions.d.ts +99 -0
  7. package/dist/capabilities/preconditions.d.ts.map +1 -0
  8. package/dist/capabilities/preconditions.js +94 -0
  9. package/dist/capabilities/preconditions.js.map +1 -0
  10. package/dist/contracts/engine.d.ts +90 -0
  11. package/dist/contracts/engine.d.ts.map +1 -0
  12. package/dist/contracts/engine.js +291 -0
  13. package/dist/contracts/engine.js.map +1 -0
  14. package/dist/contracts/index.d.ts +9 -0
  15. package/dist/contracts/index.d.ts.map +1 -0
  16. package/dist/contracts/index.js +5 -0
  17. package/dist/contracts/index.js.map +1 -0
  18. package/dist/contracts/orphans.d.ts +96 -0
  19. package/dist/contracts/orphans.d.ts.map +1 -0
  20. package/dist/contracts/orphans.js +241 -0
  21. package/dist/contracts/orphans.js.map +1 -0
  22. package/dist/contracts/paths.d.ts +132 -0
  23. package/dist/contracts/paths.d.ts.map +1 -0
  24. package/dist/contracts/paths.js +245 -0
  25. package/dist/contracts/paths.js.map +1 -0
  26. package/dist/contracts/shapes.d.ts +99 -0
  27. package/dist/contracts/shapes.d.ts.map +1 -0
  28. package/dist/contracts/shapes.js +196 -0
  29. package/dist/contracts/shapes.js.map +1 -0
  30. package/dist/governance/budget.d.ts +89 -0
  31. package/dist/governance/budget.d.ts.map +1 -0
  32. package/dist/governance/budget.js +101 -0
  33. package/dist/governance/budget.js.map +1 -0
  34. package/dist/governance/candidate-boundary.d.ts +49 -0
  35. package/dist/governance/candidate-boundary.d.ts.map +1 -0
  36. package/dist/governance/candidate-boundary.js +51 -0
  37. package/dist/governance/candidate-boundary.js.map +1 -0
  38. package/dist/governance/escalation.d.ts +63 -0
  39. package/dist/governance/escalation.d.ts.map +1 -0
  40. package/dist/governance/escalation.js +72 -0
  41. package/dist/governance/escalation.js.map +1 -0
  42. package/dist/governance/fact-boundary.d.ts +77 -0
  43. package/dist/governance/fact-boundary.d.ts.map +1 -0
  44. package/dist/governance/fact-boundary.js +91 -0
  45. package/dist/governance/fact-boundary.js.map +1 -0
  46. package/dist/governance/finding-funnel.d.ts +109 -0
  47. package/dist/governance/finding-funnel.d.ts.map +1 -0
  48. package/dist/governance/finding-funnel.js +137 -0
  49. package/dist/governance/finding-funnel.js.map +1 -0
  50. package/dist/governance/index.d.ts +11 -0
  51. package/dist/governance/index.d.ts.map +1 -0
  52. package/dist/governance/index.js +6 -0
  53. package/dist/governance/index.js.map +1 -0
  54. package/dist/graph/build.d.ts +127 -0
  55. package/dist/graph/build.d.ts.map +1 -0
  56. package/dist/graph/build.js +0 -0
  57. package/dist/graph/build.js.map +1 -0
  58. package/dist/graph/cross-language.d.ts +70 -0
  59. package/dist/graph/cross-language.d.ts.map +1 -0
  60. package/dist/graph/cross-language.js +104 -0
  61. package/dist/graph/cross-language.js.map +1 -0
  62. package/dist/graph/index.d.ts +11 -0
  63. package/dist/graph/index.d.ts.map +1 -0
  64. package/dist/graph/index.js +6 -0
  65. package/dist/graph/index.js.map +1 -0
  66. package/dist/graph/merge.d.ts +95 -0
  67. package/dist/graph/merge.d.ts.map +1 -0
  68. package/dist/graph/merge.js +131 -0
  69. package/dist/graph/merge.js.map +1 -0
  70. package/dist/graph/persist.d.ts +42 -0
  71. package/dist/graph/persist.d.ts.map +1 -0
  72. package/dist/graph/persist.js +52 -0
  73. package/dist/graph/persist.js.map +1 -0
  74. package/dist/graph/runtime-confirmation.d.ts +169 -0
  75. package/dist/graph/runtime-confirmation.d.ts.map +1 -0
  76. package/dist/graph/runtime-confirmation.js +318 -0
  77. package/dist/graph/runtime-confirmation.js.map +1 -0
  78. package/dist/index.d.ts +18 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +19 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/ledger/index.d.ts +5 -0
  83. package/dist/ledger/index.d.ts.map +1 -0
  84. package/dist/ledger/index.js +3 -0
  85. package/dist/ledger/index.js.map +1 -0
  86. package/dist/ledger/mint.d.ts +81 -0
  87. package/dist/ledger/mint.d.ts.map +1 -0
  88. package/dist/ledger/mint.js +138 -0
  89. package/dist/ledger/mint.js.map +1 -0
  90. package/dist/ledger/resolve.d.ts +124 -0
  91. package/dist/ledger/resolve.d.ts.map +1 -0
  92. package/dist/ledger/resolve.js +290 -0
  93. package/dist/ledger/resolve.js.map +1 -0
  94. package/dist/multipr/fingerprint.d.ts +56 -0
  95. package/dist/multipr/fingerprint.d.ts.map +1 -0
  96. package/dist/multipr/fingerprint.js +169 -0
  97. package/dist/multipr/fingerprint.js.map +1 -0
  98. package/dist/multipr/hidden-dependency.d.ts +120 -0
  99. package/dist/multipr/hidden-dependency.d.ts.map +1 -0
  100. package/dist/multipr/hidden-dependency.js +185 -0
  101. package/dist/multipr/hidden-dependency.js.map +1 -0
  102. package/dist/multipr/index.d.ts +14 -0
  103. package/dist/multipr/index.d.ts.map +1 -0
  104. package/dist/multipr/index.js +8 -0
  105. package/dist/multipr/index.js.map +1 -0
  106. package/dist/multipr/mechanical.d.ts +239 -0
  107. package/dist/multipr/mechanical.d.ts.map +1 -0
  108. package/dist/multipr/mechanical.js +333 -0
  109. package/dist/multipr/mechanical.js.map +1 -0
  110. package/dist/multipr/migration-heads.d.ts +160 -0
  111. package/dist/multipr/migration-heads.d.ts.map +1 -0
  112. package/dist/multipr/migration-heads.js +212 -0
  113. package/dist/multipr/migration-heads.js.map +1 -0
  114. package/dist/multipr/overlap.d.ts +186 -0
  115. package/dist/multipr/overlap.d.ts.map +1 -0
  116. package/dist/multipr/overlap.js +254 -0
  117. package/dist/multipr/overlap.js.map +1 -0
  118. package/dist/multipr/scope-store.d.ts +105 -0
  119. package/dist/multipr/scope-store.d.ts.map +1 -0
  120. package/dist/multipr/scope-store.js +182 -0
  121. package/dist/multipr/scope-store.js.map +1 -0
  122. package/dist/multipr/superseded.d.ts +133 -0
  123. package/dist/multipr/superseded.d.ts.map +1 -0
  124. package/dist/multipr/superseded.js +128 -0
  125. package/dist/multipr/superseded.js.map +1 -0
  126. package/dist/query/confirmed-facts.d.ts +56 -0
  127. package/dist/query/confirmed-facts.d.ts.map +1 -0
  128. package/dist/query/confirmed-facts.js +64 -0
  129. package/dist/query/confirmed-facts.js.map +1 -0
  130. package/dist/query/declared-value-closure.d.ts +126 -0
  131. package/dist/query/declared-value-closure.d.ts.map +1 -0
  132. package/dist/query/declared-value-closure.js +156 -0
  133. package/dist/query/declared-value-closure.js.map +1 -0
  134. package/dist/query/index.d.ts +28 -0
  135. package/dist/query/index.d.ts.map +1 -0
  136. package/dist/query/index.js +15 -0
  137. package/dist/query/index.js.map +1 -0
  138. package/dist/query/memory.d.ts +20 -0
  139. package/dist/query/memory.d.ts.map +1 -0
  140. package/dist/query/memory.js +55 -0
  141. package/dist/query/memory.js.map +1 -0
  142. package/dist/query/provider.d.ts +127 -0
  143. package/dist/query/provider.d.ts.map +1 -0
  144. package/dist/query/provider.js +54 -0
  145. package/dist/query/provider.js.map +1 -0
  146. package/dist/query/queries.d.ts +119 -0
  147. package/dist/query/queries.d.ts.map +1 -0
  148. package/dist/query/queries.js +267 -0
  149. package/dist/query/queries.js.map +1 -0
  150. package/dist/query/refusal-fetch.d.ts +114 -0
  151. package/dist/query/refusal-fetch.d.ts.map +1 -0
  152. package/dist/query/refusal-fetch.js +200 -0
  153. package/dist/query/refusal-fetch.js.map +1 -0
  154. package/dist/query/refusal-questions.d.ts +123 -0
  155. package/dist/query/refusal-questions.d.ts.map +1 -0
  156. package/dist/query/refusal-questions.js +122 -0
  157. package/dist/query/refusal-questions.js.map +1 -0
  158. package/dist/query/root-cause-score.d.ts +218 -0
  159. package/dist/query/root-cause-score.d.ts.map +1 -0
  160. package/dist/query/root-cause-score.js +251 -0
  161. package/dist/query/root-cause-score.js.map +1 -0
  162. package/dist/query/row-closure-picture.d.ts +108 -0
  163. package/dist/query/row-closure-picture.d.ts.map +1 -0
  164. package/dist/query/row-closure-picture.js +131 -0
  165. package/dist/query/row-closure-picture.js.map +1 -0
  166. package/dist/query/similar-incidents.d.ts +76 -0
  167. package/dist/query/similar-incidents.d.ts.map +1 -0
  168. package/dist/query/similar-incidents.js +169 -0
  169. package/dist/query/similar-incidents.js.map +1 -0
  170. package/dist/query/sqlite.d.ts +13 -0
  171. package/dist/query/sqlite.d.ts.map +1 -0
  172. package/dist/query/sqlite.js +54 -0
  173. package/dist/query/sqlite.js.map +1 -0
  174. package/dist/query/traverse.d.ts +110 -0
  175. package/dist/query/traverse.d.ts.map +1 -0
  176. package/dist/query/traverse.js +0 -0
  177. package/dist/query/traverse.js.map +1 -0
  178. package/dist/query/unresolved.d.ts +195 -0
  179. package/dist/query/unresolved.d.ts.map +1 -0
  180. package/dist/query/unresolved.js +225 -0
  181. package/dist/query/unresolved.js.map +1 -0
  182. package/dist/query/verification-status.d.ts +134 -0
  183. package/dist/query/verification-status.d.ts.map +1 -0
  184. package/dist/query/verification-status.js +163 -0
  185. package/dist/query/verification-status.js.map +1 -0
  186. package/dist/recording/index.d.ts +26 -0
  187. package/dist/recording/index.d.ts.map +1 -0
  188. package/dist/recording/index.js +30 -0
  189. package/dist/recording/index.js.map +1 -0
  190. package/dist/recording/migrate.d.ts +22 -0
  191. package/dist/recording/migrate.d.ts.map +1 -0
  192. package/dist/recording/migrate.js +51 -0
  193. package/dist/recording/migrate.js.map +1 -0
  194. package/dist/recording/reader.d.ts +27 -0
  195. package/dist/recording/reader.d.ts.map +1 -0
  196. package/dist/recording/reader.js +55 -0
  197. package/dist/recording/reader.js.map +1 -0
  198. package/dist/recording/redact.d.ts +63 -0
  199. package/dist/recording/redact.d.ts.map +1 -0
  200. package/dist/recording/redact.js +98 -0
  201. package/dist/recording/redact.js.map +1 -0
  202. package/dist/recording/schema.d.ts +41 -0
  203. package/dist/recording/schema.d.ts.map +1 -0
  204. package/dist/recording/schema.js +104 -0
  205. package/dist/recording/schema.js.map +1 -0
  206. package/dist/recording/writer.d.ts +120 -0
  207. package/dist/recording/writer.d.ts.map +1 -0
  208. package/dist/recording/writer.js +81 -0
  209. package/dist/recording/writer.js.map +1 -0
  210. package/dist/scoping/fanout.d.ts +86 -0
  211. package/dist/scoping/fanout.d.ts.map +1 -0
  212. package/dist/scoping/fanout.js +95 -0
  213. package/dist/scoping/fanout.js.map +1 -0
  214. package/dist/scoping/index.d.ts +10 -0
  215. package/dist/scoping/index.d.ts.map +1 -0
  216. package/dist/scoping/index.js +6 -0
  217. package/dist/scoping/index.js.map +1 -0
  218. package/dist/scoping/score.d.ts +83 -0
  219. package/dist/scoping/score.d.ts.map +1 -0
  220. package/dist/scoping/score.js +125 -0
  221. package/dist/scoping/score.js.map +1 -0
  222. package/dist/scoping/tiers.d.ts +32 -0
  223. package/dist/scoping/tiers.d.ts.map +1 -0
  224. package/dist/scoping/tiers.js +35 -0
  225. package/dist/scoping/tiers.js.map +1 -0
  226. package/dist/scoping/traverse.d.ts +60 -0
  227. package/dist/scoping/traverse.d.ts.map +1 -0
  228. package/dist/scoping/traverse.js +172 -0
  229. package/dist/scoping/traverse.js.map +1 -0
  230. package/dist/scoping/weights.d.ts +97 -0
  231. package/dist/scoping/weights.d.ts.map +1 -0
  232. package/dist/scoping/weights.js +167 -0
  233. package/dist/scoping/weights.js.map +1 -0
  234. package/dist/sources/git/diff.d.ts +53 -0
  235. package/dist/sources/git/diff.d.ts.map +1 -0
  236. package/dist/sources/git/diff.js +140 -0
  237. package/dist/sources/git/diff.js.map +1 -0
  238. package/dist/sources/git/env.d.ts +2 -0
  239. package/dist/sources/git/env.d.ts.map +1 -0
  240. package/dist/sources/git/env.js +21 -0
  241. package/dist/sources/git/env.js.map +1 -0
  242. package/dist/sources/git/history.d.ts +140 -0
  243. package/dist/sources/git/history.d.ts.map +1 -0
  244. package/dist/sources/git/history.js +314 -0
  245. package/dist/sources/git/history.js.map +1 -0
  246. package/dist/sources/git/index.d.ts +7 -0
  247. package/dist/sources/git/index.d.ts.map +1 -0
  248. package/dist/sources/git/index.js +4 -0
  249. package/dist/sources/git/index.js.map +1 -0
  250. package/dist/sources/git/source.d.ts +95 -0
  251. package/dist/sources/git/source.d.ts.map +1 -0
  252. package/dist/sources/git/source.js +0 -0
  253. package/dist/sources/git/source.js.map +1 -0
  254. package/dist/sources/migrations/dialects.d.ts +59 -0
  255. package/dist/sources/migrations/dialects.d.ts.map +1 -0
  256. package/dist/sources/migrations/dialects.js +84 -0
  257. package/dist/sources/migrations/dialects.js.map +1 -0
  258. package/dist/sources/migrations/index.d.ts +3 -0
  259. package/dist/sources/migrations/index.d.ts.map +1 -0
  260. package/dist/sources/migrations/index.js +2 -0
  261. package/dist/sources/migrations/index.js.map +1 -0
  262. package/dist/sources/migrations/read.d.ts +113 -0
  263. package/dist/sources/migrations/read.d.ts.map +1 -0
  264. package/dist/sources/migrations/read.js +270 -0
  265. package/dist/sources/migrations/read.js.map +1 -0
  266. package/dist/sources/workspace/index.d.ts +3 -0
  267. package/dist/sources/workspace/index.d.ts.map +1 -0
  268. package/dist/sources/workspace/index.js +2 -0
  269. package/dist/sources/workspace/index.js.map +1 -0
  270. package/dist/sources/workspace/workspace.d.ts +64 -0
  271. package/dist/sources/workspace/workspace.d.ts.map +1 -0
  272. package/dist/sources/workspace/workspace.js +294 -0
  273. package/dist/sources/workspace/workspace.js.map +1 -0
  274. package/dist/store/driver/driver.d.ts +82 -0
  275. package/dist/store/driver/driver.d.ts.map +1 -0
  276. package/dist/store/driver/driver.js +31 -0
  277. package/dist/store/driver/driver.js.map +1 -0
  278. package/dist/store/driver/node-sqlite.d.ts +28 -0
  279. package/dist/store/driver/node-sqlite.d.ts.map +1 -0
  280. package/dist/store/driver/node-sqlite.js +188 -0
  281. package/dist/store/driver/node-sqlite.js.map +1 -0
  282. package/dist/store/index.d.ts +33 -0
  283. package/dist/store/index.d.ts.map +1 -0
  284. package/dist/store/index.js +46 -0
  285. package/dist/store/index.js.map +1 -0
  286. package/dist/store/migrate.d.ts +68 -0
  287. package/dist/store/migrate.d.ts.map +1 -0
  288. package/dist/store/migrate.js +140 -0
  289. package/dist/store/migrate.js.map +1 -0
  290. package/dist/store/reader.d.ts +127 -0
  291. package/dist/store/reader.d.ts.map +1 -0
  292. package/dist/store/reader.js +269 -0
  293. package/dist/store/reader.js.map +1 -0
  294. package/dist/store/schema.d.ts +87 -0
  295. package/dist/store/schema.d.ts.map +1 -0
  296. package/dist/store/schema.js +322 -0
  297. package/dist/store/schema.js.map +1 -0
  298. package/dist/store/writer.d.ts +116 -0
  299. package/dist/store/writer.d.ts.map +1 -0
  300. package/dist/store/writer.js +279 -0
  301. package/dist/store/writer.js.map +1 -0
  302. package/dist/tiers/certify.d.ts +375 -0
  303. package/dist/tiers/certify.d.ts.map +1 -0
  304. package/dist/tiers/certify.js +434 -0
  305. package/dist/tiers/certify.js.map +1 -0
  306. package/dist/tiers/index.d.ts +5 -0
  307. package/dist/tiers/index.d.ts.map +1 -0
  308. package/dist/tiers/index.js +3 -0
  309. package/dist/tiers/index.js.map +1 -0
  310. package/dist/tiers/ladder.d.ts +73 -0
  311. package/dist/tiers/ladder.d.ts.map +1 -0
  312. package/dist/tiers/ladder.js +197 -0
  313. package/dist/tiers/ladder.js.map +1 -0
  314. package/dist/validation/config-graph.d.ts +104 -0
  315. package/dist/validation/config-graph.d.ts.map +1 -0
  316. package/dist/validation/config-graph.js +205 -0
  317. package/dist/validation/config-graph.js.map +1 -0
  318. package/dist/validation/env.d.ts +243 -0
  319. package/dist/validation/env.d.ts.map +1 -0
  320. package/dist/validation/env.js +447 -0
  321. package/dist/validation/env.js.map +1 -0
  322. package/dist/validation/index.d.ts +16 -0
  323. package/dist/validation/index.d.ts.map +1 -0
  324. package/dist/validation/index.js +9 -0
  325. package/dist/validation/index.js.map +1 -0
  326. package/dist/validation/join-substitution.d.ts +186 -0
  327. package/dist/validation/join-substitution.d.ts.map +1 -0
  328. package/dist/validation/join-substitution.js +191 -0
  329. package/dist/validation/join-substitution.js.map +1 -0
  330. package/dist/validation/migrations.d.ts +133 -0
  331. package/dist/validation/migrations.d.ts.map +1 -0
  332. package/dist/validation/migrations.js +315 -0
  333. package/dist/validation/migrations.js.map +1 -0
  334. package/dist/validation/nodes.d.ts +32 -0
  335. package/dist/validation/nodes.d.ts.map +1 -0
  336. package/dist/validation/nodes.js +45 -0
  337. package/dist/validation/nodes.js.map +1 -0
  338. package/dist/validation/rollback.d.ts +124 -0
  339. package/dist/validation/rollback.d.ts.map +1 -0
  340. package/dist/validation/rollback.js +302 -0
  341. package/dist/validation/rollback.js.map +1 -0
  342. package/dist/validation/route-drift.d.ts +108 -0
  343. package/dist/validation/route-drift.d.ts.map +1 -0
  344. package/dist/validation/route-drift.js +103 -0
  345. package/dist/validation/route-drift.js.map +1 -0
  346. package/dist/validation/sources.d.ts +153 -0
  347. package/dist/validation/sources.d.ts.map +1 -0
  348. package/dist/validation/sources.js +409 -0
  349. package/dist/validation/sources.js.map +1 -0
  350. package/dist/worker/index.d.ts +5 -0
  351. package/dist/worker/index.d.ts.map +1 -0
  352. package/dist/worker/index.js +3 -0
  353. package/dist/worker/index.js.map +1 -0
  354. package/dist/worker/pool.d.ts +73 -0
  355. package/dist/worker/pool.d.ts.map +1 -0
  356. package/dist/worker/pool.js +203 -0
  357. package/dist/worker/pool.js.map +1 -0
  358. package/dist/worker/protocol.d.ts +91 -0
  359. package/dist/worker/protocol.d.ts.map +1 -0
  360. package/dist/worker/protocol.js +19 -0
  361. package/dist/worker/protocol.js.map +1 -0
  362. package/dist/worker/traversal.worker.d.ts +25 -0
  363. package/dist/worker/traversal.worker.d.ts.map +1 -0
  364. package/dist/worker/traversal.worker.js +124 -0
  365. package/dist/worker/traversal.worker.js.map +1 -0
  366. package/package.json +29 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The backing for `refusal_fetch` — `ai-tool-contract.md` §5.4, §8 endpoint 14.
3
+ *
4
+ * ## The premise this corrects
5
+ *
6
+ * `ai-layer-plan.md` §3 originally claimed this tool was cheap because
7
+ * "`askableHandle` exists in core and nothing serves it." Checked against
8
+ * `refusal-questions.ts` rather than taken on the plan's word, both halves
9
+ * were false: `askableHandle` is the *askable predicate* — `blockedBy` only
10
+ * for `refusalClass === "value-unknown"`, `null` for `capability-gap` /
11
+ * `out-of-scope` / `varies-per-call` / unset — and it is already served, by
12
+ * `packages/mcp/src/tools/questions.ts` via `groupRefusalsIntoQuestions`.
13
+ * The contract's own §5.4 records the correction; this module is the new
14
+ * work it points at.
15
+ *
16
+ * ## Why `groupRefusalsIntoQuestions` cannot be reused here
17
+ *
18
+ * That grouper drops every row that is not `value-unknown` **by design** —
19
+ * correct for its own purpose (a question queue only wants rows a human
20
+ * sentence can close), wrong for this one. A `refusal_fetch` that silently
21
+ * returned only the askable subset would be the empty-denominator defect
22
+ * (`ai-tool-contract.md` §1's "count over something that was not there,
23
+ * presented as a count of zero") wearing technique 4's clothes — the same
24
+ * defect class this project has already hit three times elsewhere
25
+ * (shopizer's 0-of-370 route count, airbyte's "not on disk", koa's silent
26
+ * `routers.size === 0`). This module therefore counts and pages **every**
27
+ * `UnresolvedRef`, of every `refusalClass` and unset alike.
28
+ *
29
+ * ## Call-site trap, named so it is not rediscovered the expensive way
30
+ *
31
+ * `store/reader.ts#unresolvedRefs` defaults `classified: true`, which
32
+ * restricts to `attrs IS NOT NULL` — exactly the rows a producer has
33
+ * classified at all. A caller that wires this module up to that reader
34
+ * without passing `{ classified: false }` will silently exclude every
35
+ * *unclassified* row from `count`, reproducing the same defect this module
36
+ * exists to prevent, one call site later. `summariseRefusals`/
37
+ * `fetchRefusalPage` cannot detect that mistake — they only know the
38
+ * population they were handed — so the honesty of `count` here is only as
39
+ * good as the completeness of what a caller passes in.
40
+ *
41
+ * ## The handle
42
+ *
43
+ * Stateless and self-describing (a base64url-encoded cursor plus a content
44
+ * fingerprint of the population it was issued against), not a session-cache
45
+ * lookup — so redemption is correct across process restarts and does not
46
+ * need this module to keep anything in memory between calls. An MCP surface
47
+ * that wants a prettier, shorter token (the contract's own illustrative
48
+ * `ref_9f2c1a`) is free to wrap this in a session cache; that is a
49
+ * presentation choice for P27, not a correctness requirement for this
50
+ * module.
51
+ *
52
+ * `UnresolvedRef` carries no stable id once it has crossed the store
53
+ * boundary (`store/reader.ts#toUnresolvedRef` does not carry the row's
54
+ * content-hash `id` column into the IR shape), so this module derives its
55
+ * own deterministic ordering key from the fields the type does carry. That
56
+ * key is what makes "same handle + same underlying population → same page
57
+ * ordering every time" true regardless of what order the caller's array
58
+ * happens to be in — a concatenation from several producers, or a
59
+ * differently-ordered SQL read, must not change which row lands on which
60
+ * page.
61
+ *
62
+ * Redeeming a handle that cannot be decoded, or whose population fingerprint
63
+ * no longer matches what is being read, is `refused` with a named reason —
64
+ * never a page that happens to be empty, and never a page silently drawn
65
+ * from the wrong cursor position. That is the rule the whole contract rests
66
+ * on (§1: `refused` is a fact about Descry, never a guess dressed as one),
67
+ * and this endpoint is the easiest place in the system to violate it
68
+ * silently.
69
+ */
70
+ import { createHash } from "node:crypto";
71
+ const DEFAULT_PAGE_SIZE = 50;
72
+ const HANDLE_VERSION = 1;
73
+ const HANDLE_PREFIX = "ref_";
74
+ function isHandlePayload(value) {
75
+ if (typeof value !== "object" || value === null)
76
+ return false;
77
+ const v = value;
78
+ return (typeof v["v"] === "number" &&
79
+ typeof v["offset"] === "number" &&
80
+ typeof v["pageSize"] === "number" &&
81
+ typeof v["populationHash"] === "string");
82
+ }
83
+ /**
84
+ * A deterministic ordering key for one refusal row, built from every field
85
+ * `UnresolvedRef` carries except `attrs` — the same fields the store's own
86
+ * `unresolved_refs.id` content hash is built from (`schema.ts`'s own
87
+ * comment: classification is not part of what makes two refusals the same
88
+ * refusal). Restated here rather than imported, because the store's `id`
89
+ * never crosses into the `UnresolvedRef` shape this module receives.
90
+ */
91
+ function sortKey(ref) {
92
+ return [
93
+ ref.fromNodeId,
94
+ ref.edgeType,
95
+ ref.rawTarget,
96
+ ref.file ?? "",
97
+ ref.line === null ? "" : String(ref.line),
98
+ ref.producedBy,
99
+ ref.reason,
100
+ ].join("|");
101
+ }
102
+ function orderedBy(refs) {
103
+ return [...refs].sort((a, b) => {
104
+ const ka = sortKey(a);
105
+ const kb = sortKey(b);
106
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
107
+ });
108
+ }
109
+ /** A content fingerprint of the whole ordered population — the identity check that turns a
110
+ * graph change between "summarise" and "fetch" into an honest `refused` rather than a wrong page. */
111
+ function populationHash(ordered) {
112
+ const h = createHash("sha256");
113
+ for (const ref of ordered) {
114
+ h.update(sortKey(ref));
115
+ h.update("|");
116
+ }
117
+ return h.digest("hex");
118
+ }
119
+ function encodeHandle(payload) {
120
+ return HANDLE_PREFIX + Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
121
+ }
122
+ function decodeHandle(handle) {
123
+ if (!handle.startsWith(HANDLE_PREFIX))
124
+ return null;
125
+ try {
126
+ const json = Buffer.from(handle.slice(HANDLE_PREFIX.length), "base64url").toString("utf8");
127
+ const parsed = JSON.parse(json);
128
+ return isHandlePayload(parsed) ? parsed : null;
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ }
134
+ /**
135
+ * Summarise a population of refusal rows into the one-line shape the
136
+ * envelope carries. `refs` must already be the complete population the
137
+ * caller intends to disclose — see the module header's call-site trap for
138
+ * the classic way to under-supply it.
139
+ */
140
+ export function summariseRefusals(refs, pageSize = DEFAULT_PAGE_SIZE) {
141
+ if (refs.length === 0) {
142
+ return { count: 0, exemplar: null, handle: null };
143
+ }
144
+ const ordered = orderedBy(refs);
145
+ const hash = populationHash(ordered);
146
+ return {
147
+ count: refs.length,
148
+ exemplar: ordered[0].reason,
149
+ handle: encodeHandle({
150
+ v: HANDLE_VERSION,
151
+ offset: 0,
152
+ pageSize: Math.max(1, Math.floor(pageSize)),
153
+ populationHash: hash,
154
+ }),
155
+ };
156
+ }
157
+ /**
158
+ * Redeem a handle against the current population. `refs` need not be
159
+ * pre-sorted or supplied in the same order as the call that minted the
160
+ * handle — ordering is re-derived deterministically every time.
161
+ *
162
+ * Three distinct ways to be `refused`, each named: the handle cannot be
163
+ * decoded at all; the population's content fingerprint no longer matches
164
+ * (the graph moved since the handle was issued); or the cursor position the
165
+ * handle names does not exist in the current population (only reachable via
166
+ * a handle not issued by this module's own chain, since a well-formed
167
+ * `nextHandle` is never offered past the end).
168
+ */
169
+ export function fetchRefusalPage(refs, handle) {
170
+ const payload = decodeHandle(handle);
171
+ if (payload === null) {
172
+ return {
173
+ state: "refused",
174
+ reason: `refusal_fetch handle "${handle}" cannot be decoded — it was not issued by summariseRefusals/fetchRefusalPage, or it is corrupted`,
175
+ };
176
+ }
177
+ const ordered = orderedBy(refs);
178
+ const hash = populationHash(ordered);
179
+ if (hash !== payload.populationHash) {
180
+ return {
181
+ state: "refused",
182
+ reason: "the refusal ledger has changed since this handle was issued (rows added, removed or reclassified) — " +
183
+ "re-fetch from a fresh summary rather than trusting this page",
184
+ };
185
+ }
186
+ if (payload.offset < 0 || payload.offset >= ordered.length) {
187
+ return {
188
+ state: "refused",
189
+ reason: `this handle's cursor position (${payload.offset}) does not exist in a ledger of ${ordered.length} row(s)`,
190
+ };
191
+ }
192
+ const pageSize = Math.max(1, Math.floor(payload.pageSize));
193
+ const items = ordered.slice(payload.offset, payload.offset + pageSize);
194
+ const nextOffset = payload.offset + items.length;
195
+ const nextHandle = nextOffset < ordered.length
196
+ ? encodeHandle({ v: HANDLE_VERSION, offset: nextOffset, pageSize, populationHash: hash })
197
+ : null;
198
+ return { state: "ok", items, total: ordered.length, nextHandle };
199
+ }
200
+ //# sourceMappingURL=refusal-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusal-fetch.js","sourceRoot":"","sources":["../../src/query/refusal-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The grouper — turns classified refusal rows into questions a person can
3
+ * actually answer.
4
+ *
5
+ * DEC-242 gave `UnresolvedRef` a machine-readable handle (`attrs.blockedBy`)
6
+ * and a producer-declared answerability class (`attrs.refusalClass`). Neither
7
+ * one is a question by itself: a ledger of 136 rows is still 136 rows until
8
+ * something collapses the ones that share a cause into one thing to ask.
9
+ * That collapse is all this module does.
10
+ *
11
+ * ## The filter is the whole point
12
+ *
13
+ * Only `"value-unknown"` rows become questions. `"capability-gap"`,
14
+ * `"out-of-scope"`, `"varies-per-call"` and **unset** never do — DEC-242's own
15
+ * measurement is why: 92 of 95 real C#/Java refusal rows are
16
+ * `"capability-gap"`, and asking a human to resolve one of those, or a
17
+ * `"varies-per-call"` row (*"what is `url`?"*), is a category error, not an
18
+ * unanswered question. Unset is the sharpest trap: it looks identical to
19
+ * `"value-unknown"` in shape (a row, a `rawTarget`, a `reason`) and is
20
+ * silently the majority case until every producer sets the field. Defaulting
21
+ * it to askable is exactly how an unaudited row reaches a user.
22
+ *
23
+ * ## Grouping key is `(repo, blockedBy)`, and this file only ever sees one repo
24
+ *
25
+ * `env.BACKEND_API_BASE_URL` in `sherpa-cf-worker` and the byte-identical
26
+ * string in a different repository are different facts about different
27
+ * runtimes — DEC-223 §3 already committed to exact-match scoped to one repo,
28
+ * and measured the failure mode directly (both repos use the same env var
29
+ * name for different backends). `groupRefusalsIntoQuestions` takes one repo's
30
+ * rows and returns that repo's questions; it has no way to merge across
31
+ * repos because it never sees more than one. `groupRefusalsAcrossRepos` is the
32
+ * only entry point that touches more than one repo's rows, and it keeps them
33
+ * in separate groups by construction — see its own doc for what that means in
34
+ * practice, and the test that proves it.
35
+ *
36
+ * ## What this does not do
37
+ *
38
+ * It does not run config resolution. DEC-242's ordering constraint —
39
+ * `refusalClass` is assigned only after a producer checks `.env` files,
40
+ * deploy config and the like — is the producer's job, upstream of this
41
+ * module entirely. A `"value-unknown"` row reaching here is trusted at face
42
+ * value; second-guessing it here would duplicate work a producer already did
43
+ * or silently paper over one that skipped it, and neither is this module's
44
+ * business to detect.
45
+ *
46
+ * It does not store, dedupe against a previously-answered `ConfirmedFact`
47
+ * (DEC-223 §3), or decide where a question surfaces. It is the piece in
48
+ * between: rows in, questions out, with everything dropped along the way
49
+ * counted rather than discarded.
50
+ */
51
+ import type { UnresolvedRef } from "@descryy/ir";
52
+ /** One call site behind a question — enough for a reader to go look, not just a count. */
53
+ export interface RefusalSite {
54
+ readonly fromNodeId: string;
55
+ readonly file: string | null;
56
+ readonly line: number | null;
57
+ /** The callee text, unchanged from the row — `fetch`, `api.get`. */
58
+ readonly rawTarget: string;
59
+ }
60
+ /** One question: "what does `blockedBy` evaluate to, in `repo`?" (DEC-223 §2), plus its evidence. */
61
+ export interface RefusalQuestion {
62
+ readonly repo: string;
63
+ readonly blockedBy: string;
64
+ readonly sites: readonly RefusalSite[];
65
+ }
66
+ /**
67
+ * What every accepted row not made into a question was instead, one bucket
68
+ * per cause. Never a single "dropped" count — `dropped["unset"]` and
69
+ * `dropped["capability-gap"]` are different facts with different remedies,
70
+ * and collapsing them would recreate the conflation DEC-223's own
71
+ * `query/unresolved.ts` header already measured once.
72
+ *
73
+ * `"value-unknown-no-handle"` is its own bucket rather than folded into
74
+ * `"value-unknown"`: a row can legally declare itself askable while naming no
75
+ * expression to ask about (`attrs.blockedBy` absent or `null`, DEC-242) — a
76
+ * producer contradiction, not the same thing as a row this module correctly
77
+ * turned into a question, and not the same thing as a row a producer
78
+ * correctly marked unanswerable either.
79
+ */
80
+ export interface RefusalDropCounts {
81
+ readonly [refusalClass: string]: number;
82
+ }
83
+ export interface GroupingResult {
84
+ readonly repo: string;
85
+ readonly totalRows: number;
86
+ readonly questions: readonly RefusalQuestion[];
87
+ readonly dropped: RefusalDropCounts;
88
+ }
89
+ /**
90
+ * The askable predicate, exported so a second consumer (DEC-244's
91
+ * `applyDeclaredValues`) does not re-derive it a third time — `unresolved.ts`
92
+ * and this file already had one independent copy each before DEC-244, and a
93
+ * third divergent copy is exactly the drift DEC-098's own guard-omission
94
+ * incident (§3.1) named. Returns the row's `blockedBy` when, and only when,
95
+ * this row is `"value-unknown"` with a non-empty handle to ask about — `null`
96
+ * for every other case, unset included.
97
+ */
98
+ export declare function askableHandle(ref: UnresolvedRef): string | null;
99
+ /**
100
+ * Group one repository's refusal rows into questions.
101
+ *
102
+ * The reconciliation this makes checkable: every row in `refs` is accounted
103
+ * for in exactly one place — a site under some question's `sites`, or a count
104
+ * under `dropped` — so `totalRows === sum(dropped) + sum(questions[].sites.length)`
105
+ * always. The DEC-241-shaped law, one level down again.
106
+ */
107
+ export declare function groupRefusalsIntoQuestions(repo: string, refs: readonly UnresolvedRef[]): GroupingResult;
108
+ /**
109
+ * Group several repositories' rows without ever merging across them.
110
+ *
111
+ * This exists because the risk this module guards against is a *caller's*
112
+ * mistake, not this file's: nothing stops a consumer from concatenating two
113
+ * repos' `unresolved` arrays before calling `groupRefusalsIntoQuestions` once,
114
+ * and a byte-identical `blockedBy` string would then merge two unrelated
115
+ * facts into one question (DEC-223 §3's measured failure). This function is
116
+ * the version of the API where that mistake cannot be made — one
117
+ * `GroupingResult` per repo, always, however many repos come in.
118
+ */
119
+ export declare function groupRefusalsAcrossRepos(inputs: readonly {
120
+ readonly repo: string;
121
+ readonly refs: readonly UnresolvedRef[];
122
+ }[]): readonly GroupingResult[];
123
+ //# sourceMappingURL=refusal-questions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusal-questions.d.ts","sourceRoot":"","sources":["../../src/query/refusal-questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qGAAqG;AACrG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAYD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,aAAa,EAAE,GAC7B,cAAc,CA6BhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,EAAE,GACpF,SAAS,cAAc,EAAE,CAE3B"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The grouper — turns classified refusal rows into questions a person can
3
+ * actually answer.
4
+ *
5
+ * DEC-242 gave `UnresolvedRef` a machine-readable handle (`attrs.blockedBy`)
6
+ * and a producer-declared answerability class (`attrs.refusalClass`). Neither
7
+ * one is a question by itself: a ledger of 136 rows is still 136 rows until
8
+ * something collapses the ones that share a cause into one thing to ask.
9
+ * That collapse is all this module does.
10
+ *
11
+ * ## The filter is the whole point
12
+ *
13
+ * Only `"value-unknown"` rows become questions. `"capability-gap"`,
14
+ * `"out-of-scope"`, `"varies-per-call"` and **unset** never do — DEC-242's own
15
+ * measurement is why: 92 of 95 real C#/Java refusal rows are
16
+ * `"capability-gap"`, and asking a human to resolve one of those, or a
17
+ * `"varies-per-call"` row (*"what is `url`?"*), is a category error, not an
18
+ * unanswered question. Unset is the sharpest trap: it looks identical to
19
+ * `"value-unknown"` in shape (a row, a `rawTarget`, a `reason`) and is
20
+ * silently the majority case until every producer sets the field. Defaulting
21
+ * it to askable is exactly how an unaudited row reaches a user.
22
+ *
23
+ * ## Grouping key is `(repo, blockedBy)`, and this file only ever sees one repo
24
+ *
25
+ * `env.BACKEND_API_BASE_URL` in `sherpa-cf-worker` and the byte-identical
26
+ * string in a different repository are different facts about different
27
+ * runtimes — DEC-223 §3 already committed to exact-match scoped to one repo,
28
+ * and measured the failure mode directly (both repos use the same env var
29
+ * name for different backends). `groupRefusalsIntoQuestions` takes one repo's
30
+ * rows and returns that repo's questions; it has no way to merge across
31
+ * repos because it never sees more than one. `groupRefusalsAcrossRepos` is the
32
+ * only entry point that touches more than one repo's rows, and it keeps them
33
+ * in separate groups by construction — see its own doc for what that means in
34
+ * practice, and the test that proves it.
35
+ *
36
+ * ## What this does not do
37
+ *
38
+ * It does not run config resolution. DEC-242's ordering constraint —
39
+ * `refusalClass` is assigned only after a producer checks `.env` files,
40
+ * deploy config and the like — is the producer's job, upstream of this
41
+ * module entirely. A `"value-unknown"` row reaching here is trusted at face
42
+ * value; second-guessing it here would duplicate work a producer already did
43
+ * or silently paper over one that skipped it, and neither is this module's
44
+ * business to detect.
45
+ *
46
+ * It does not store, dedupe against a previously-answered `ConfirmedFact`
47
+ * (DEC-223 §3), or decide where a question surfaces. It is the piece in
48
+ * between: rows in, questions out, with everything dropped along the way
49
+ * counted rather than discarded.
50
+ */
51
+ function classOf(ref) {
52
+ const cls = ref.attrs?.["refusalClass"];
53
+ return typeof cls === "string" ? cls : "unset";
54
+ }
55
+ function handleOf(ref) {
56
+ const blockedBy = ref.attrs?.["blockedBy"];
57
+ return typeof blockedBy === "string" && blockedBy !== "" ? blockedBy : null;
58
+ }
59
+ /**
60
+ * The askable predicate, exported so a second consumer (DEC-244's
61
+ * `applyDeclaredValues`) does not re-derive it a third time — `unresolved.ts`
62
+ * and this file already had one independent copy each before DEC-244, and a
63
+ * third divergent copy is exactly the drift DEC-098's own guard-omission
64
+ * incident (§3.1) named. Returns the row's `blockedBy` when, and only when,
65
+ * this row is `"value-unknown"` with a non-empty handle to ask about — `null`
66
+ * for every other case, unset included.
67
+ */
68
+ export function askableHandle(ref) {
69
+ return classOf(ref) === "value-unknown" ? handleOf(ref) : null;
70
+ }
71
+ /**
72
+ * Group one repository's refusal rows into questions.
73
+ *
74
+ * The reconciliation this makes checkable: every row in `refs` is accounted
75
+ * for in exactly one place — a site under some question's `sites`, or a count
76
+ * under `dropped` — so `totalRows === sum(dropped) + sum(questions[].sites.length)`
77
+ * always. The DEC-241-shaped law, one level down again.
78
+ */
79
+ export function groupRefusalsIntoQuestions(repo, refs) {
80
+ const groups = new Map();
81
+ const dropped = {};
82
+ const drop = (bucket) => {
83
+ dropped[bucket] = (dropped[bucket] ?? 0) + 1;
84
+ };
85
+ for (const ref of refs) {
86
+ const cls = classOf(ref);
87
+ if (cls !== "value-unknown") {
88
+ drop(cls);
89
+ continue;
90
+ }
91
+ const handle = askableHandle(ref);
92
+ if (handle === null) {
93
+ drop("value-unknown-no-handle");
94
+ continue;
95
+ }
96
+ const site = { fromNodeId: ref.fromNodeId, file: ref.file, line: ref.line, rawTarget: ref.rawTarget };
97
+ const existing = groups.get(handle);
98
+ if (existing === undefined)
99
+ groups.set(handle, [site]);
100
+ else
101
+ existing.push(site);
102
+ }
103
+ const questions = [...groups.entries()]
104
+ .map(([blockedBy, sites]) => ({ repo, blockedBy, sites: [...sites] }))
105
+ .sort((a, b) => b.sites.length - a.sites.length || a.blockedBy.localeCompare(b.blockedBy));
106
+ return { repo, totalRows: refs.length, questions, dropped };
107
+ }
108
+ /**
109
+ * Group several repositories' rows without ever merging across them.
110
+ *
111
+ * This exists because the risk this module guards against is a *caller's*
112
+ * mistake, not this file's: nothing stops a consumer from concatenating two
113
+ * repos' `unresolved` arrays before calling `groupRefusalsIntoQuestions` once,
114
+ * and a byte-identical `blockedBy` string would then merge two unrelated
115
+ * facts into one question (DEC-223 §3's measured failure). This function is
116
+ * the version of the API where that mistake cannot be made — one
117
+ * `GroupingResult` per repo, always, however many repos come in.
118
+ */
119
+ export function groupRefusalsAcrossRepos(inputs) {
120
+ return inputs.map(({ repo, refs }) => groupRefusalsIntoQuestions(repo, refs));
121
+ }
122
+ //# sourceMappingURL=refusal-questions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusal-questions.js","sourceRoot":"","sources":["../../src/query/refusal-questions.ts"],"names":[],"mappings":"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"}