@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,104 @@
1
+ /**
2
+ * The call-recording schema — `ai-tool-contract.md` §6, rule 5: "every call is
3
+ * recorded" (§41.15), the basis of replay (`ai-layer-plan.md` §9 layer 6,
4
+ * §12's reproducibility requirement).
5
+ *
6
+ * A separate store from `../store` on purpose, not an extra table bolted onto
7
+ * the graph schema:
8
+ *
9
+ * 1. **The two version numbers move for unrelated reasons.** `IR_SCHEMA_VERSION`
10
+ * bumps when the graph's own shape changes; this schema bumps when the
11
+ * recording format changes. Coupling them would force a recording-format
12
+ * change to carry a graph rebuild, and vice versa.
13
+ * 2. **Nothing here is "derived."** DEC-017's derived/earned split exists
14
+ * because derived rows can be regenerated from source. A recorded call is a
15
+ * fact about one already-finished execution — there is no source to
16
+ * regenerate it from, ever. Every table below is earned in DEC-017's sense,
17
+ * and the version check refuses on mismatch rather than offering a
18
+ * `dropDerived()`-equivalent: there is nothing safe to drop.
19
+ * 3. Same reasoning DEC-018 gives for the graph store applies here unchanged:
20
+ * `node:sqlite` behind the existing `SqlDriver` interface, not a second
21
+ * persistence technology. This module imports the driver, it does not
22
+ * reinvent one.
23
+ *
24
+ * ## Unredacted material — read before wiring this to a live tool boundary
25
+ *
26
+ * Contract §6: redaction happens *at the tool boundary, before the model
27
+ * call*. **This store holds whatever `arguments` and `result` it is given,
28
+ * verbatim, unless a redactor was supplied.** `recordCall`'s `redact` option
29
+ * (`writer.ts`) is the seam; `redact.ts`'s `createRecordingRedactor` is a
30
+ * real, tested implementation of it, given the caller's own configured
31
+ * secret values. No live call site in this repo constructs and passes one
32
+ * today — recording is not yet wired into the MCP server's or
33
+ * `descry-runtime`'s actual tool dispatch — so every row written by this
34
+ * repo's current callers is still unredacted. Treat a database file under
35
+ * this schema as containing the same sensitive material the tool calls
36
+ * themselves carried, until whoever wires the live call path passes a
37
+ * redactor built from real secret values.
38
+ */
39
+ export const RECORDING_SCHEMA_VERSION = 1;
40
+ export const SCHEMA_SQL = `
41
+ -- ---------------------------------------------------------------------------
42
+ -- recording_meta — same role as the graph store's graph_meta: identity of the
43
+ -- store itself, checked before anything else is trusted.
44
+ -- ---------------------------------------------------------------------------
45
+ CREATE TABLE IF NOT EXISTS recording_meta (
46
+ key TEXT PRIMARY KEY,
47
+ value TEXT NOT NULL
48
+ ) STRICT;
49
+
50
+ -- ---------------------------------------------------------------------------
51
+ -- recorded_runs — one row per AI-layer run. The graph stamp (commit_sha,
52
+ -- built_at, ir_schema_version) is captured ONCE here, per §12: replay needs to
53
+ -- know what graph the run saw, not re-derive it from the first call.
54
+ --
55
+ -- prompt_version and model_id are present and nullable, never a column that
56
+ -- does not exist: §3's rule that "a missing field and a null field read
57
+ -- identically to a careless caller, and only one of them is honest" applies
58
+ -- to a database column exactly as it applies to the wire envelope. No model
59
+ -- calls exist yet in this repo, so both are null on every row written today —
60
+ -- that is a true statement about the current build, not a gap in the schema.
61
+ --
62
+ -- next_seq is the ordering counter writer.ts increments atomically with
63
+ -- every insert into recorded_calls, so "what order did these calls happen in"
64
+ -- is answerable without trusting wall-clock timestamps, which two calls in the
65
+ -- same millisecond cannot distinguish.
66
+ -- ---------------------------------------------------------------------------
67
+ CREATE TABLE IF NOT EXISTS recorded_runs (
68
+ id TEXT PRIMARY KEY,
69
+ started_at INTEGER NOT NULL,
70
+ commit_sha TEXT,
71
+ built_at TEXT,
72
+ ir_schema_version INTEGER,
73
+ prompt_version TEXT,
74
+ model_id TEXT,
75
+ next_seq INTEGER NOT NULL DEFAULT 0
76
+ ) STRICT;
77
+
78
+ -- ---------------------------------------------------------------------------
79
+ -- recorded_calls — one row per tool call. (run_id, seq) is the primary key
80
+ -- rather than an autoincrement id: ordering within a run is the whole point of
81
+ -- seq, and a composite key makes "give me run X in order" a primary-key range
82
+ -- scan instead of a sort.
83
+ --
84
+ -- arguments and result are stored as canonicalJson of exactly what the
85
+ -- caller passed — the full envelope, not a summary. Dropping a field here
86
+ -- (say, disclosures, or the resolution level buried in graph) would make
87
+ -- this store unable to reproduce whatever the finding drew from that field,
88
+ -- which is the one failure mode replay exists to rule out.
89
+ -- ---------------------------------------------------------------------------
90
+ CREATE TABLE IF NOT EXISTS recorded_calls (
91
+ run_id TEXT NOT NULL REFERENCES recorded_runs(id) ON DELETE CASCADE,
92
+ seq INTEGER NOT NULL,
93
+ tool TEXT NOT NULL,
94
+ tool_version TEXT NOT NULL,
95
+ class TEXT NOT NULL,
96
+ arguments TEXT NOT NULL,
97
+ result TEXT NOT NULL,
98
+ duration_ms INTEGER NOT NULL,
99
+ cost REAL,
100
+ recorded_at INTEGER NOT NULL,
101
+ PRIMARY KEY (run_id, seq)
102
+ ) STRICT;
103
+ `;
104
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/recording/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+DzB,CAAC"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Recording a run and its calls — `ai-tool-contract.md` §6, rule 5 (§41.15).
3
+ *
4
+ * ## Ordering is a counter, not a clock
5
+ *
6
+ * Two calls can land in the same millisecond, so `recorded_at` cannot be the
7
+ * ordering key. `recorded_runs.next_seq` is incremented atomically with every
8
+ * insert into `recorded_calls`, in the same transaction, so "what order did
9
+ * these happen in" is answerable from the primary key alone — the property
10
+ * §12's replay requirement depends on ("same inputs + same recorded responses
11
+ * = the same finding, byte for byte" only holds if a replayer can tell what
12
+ * order to feed them back in).
13
+ *
14
+ * ## `recordCall` cannot change the tool's own return
15
+ *
16
+ * A caller is expected to call the real tool, get its result, and *then* call
17
+ * `recordCall` — recording is telemetry about a call that has already
18
+ * happened, never a gate on it. So `recordCall` never throws: every failure
19
+ * inside it — no such run, a driver error, `arguments`/`result` containing
20
+ * something `canonicalJson` refuses (a `bigint`, a top-level `undefined`) — is
21
+ * caught here and reported back as `{ recorded: false, error }`. A caller that
22
+ * ignores the return value still gets its tool result untouched. This is
23
+ * `CLAUDE.md`'s honest-degradation rule applied to the recorder itself: a
24
+ * failed recording is disclosed in the return value, never silent, and never
25
+ * fatal to the call it was recording.
26
+ *
27
+ * ## Redaction is a documented seam
28
+ *
29
+ * Contract §6 puts redaction at the tool boundary, before the model call.
30
+ * `RecordCallOptions.redact`, when supplied, runs on the call *before* it is
31
+ * serialised, so nothing unredacted ever reaches `canonicalJson` once a
32
+ * redactor is passed in. `./redact.ts`'s `createRecordingRedactor` is a real
33
+ * implementation of that seam, ported from the model-client redaction seam in
34
+ * descry-desktop — see its module header. It is still opt-in rather than
35
+ * automatic here: this module has no way to know a deployment's actual
36
+ * secret values on its own, only the caller does, so a caller must construct
37
+ * the redactor and pass it. **No caller in this repo does that yet** — the
38
+ * MCP server's tool dispatch and `descry-runtime`'s do not call `recordCall`
39
+ * at all today (that wiring is separate, P3-boundary work) — so every row
40
+ * this module writes today is still exactly what the tool call carried. See
41
+ * `schema.ts`'s module header before treating a recording database file as
42
+ * safe to hand around.
43
+ */
44
+ import { type ToolClass } from "@descryy/ir";
45
+ import type { SqlDriver } from "../store/driver/driver.ts";
46
+ /**
47
+ * The graph stamp, captured once per run. `null` where the graph was not
48
+ * consulted — never omitted, for the same reason the wire envelope's
49
+ * `graph.resolutionFloor` is never absent (contract §3): a missing field and a
50
+ * null field read identically to a careless caller, and only one of them is
51
+ * honest about what this run actually saw.
52
+ */
53
+ export interface RunStamp {
54
+ readonly commitSha: string | null;
55
+ readonly builtAt: string | null;
56
+ readonly irSchemaVersion: number | null;
57
+ }
58
+ export interface StartRunOptions {
59
+ /**
60
+ * §12: present and nullable, **and null on every row this store will ever
61
+ * hold.** That is a settled fact now, not a pending wiring job.
62
+ *
63
+ * This comment used to say a caller in `descry-desktop/packages/pipeline`
64
+ * would eventually pass a value here. It will not, and the reason is
65
+ * structural rather than a matter of scheduling: this is the **tool-call**
66
+ * recording store, and `descry-desktop` never calls into it — measured, a
67
+ * grep for `startRun(`, `recordCall(` and `readRun(` across every `src`
68
+ * directory in that repository returns no reference to this module. It
69
+ * records model calls in its own `model_recording_attempts` table
70
+ * (`packages/session/src/model-recording/`), which carries its own
71
+ * `prompt_version` column, **per attempt** — because one run of that
72
+ * pipeline renders eight distinct prompts, and one column once per run
73
+ * cannot carry eight values without inventing a summary of seven of them.
74
+ *
75
+ * The column stays. A store that records tool calls has an honest place to
76
+ * name the prompt a run was issued under, if this repository ever grows a
77
+ * caller that knows one. What it does not have is a caller today, and
78
+ * saying "until that wiring lands" implied one was on its way.
79
+ *
80
+ * Corrected by PF-1 of `documents/plans/prompt-fingerprinting.md`.
81
+ */
82
+ readonly promptVersion?: string | null;
83
+ /** §12: present and nullable. No model layer exists yet, so always null today. */
84
+ readonly modelId?: string | null;
85
+ /** Injectable so determinism proofs are not at the mercy of the wall clock. */
86
+ readonly now?: () => number;
87
+ /** Injectable so determinism proofs are not at the mercy of `randomUUID`. */
88
+ readonly id?: () => string;
89
+ }
90
+ export declare function startRun(driver: SqlDriver, stamp: RunStamp, options?: StartRunOptions): {
91
+ runId: string;
92
+ };
93
+ /** One tool call, as the contract envelope defines it (§2, §6) — never a summary. */
94
+ export interface ToolCallRecord {
95
+ readonly tool: string;
96
+ readonly toolVersion: string;
97
+ readonly class: ToolClass;
98
+ /** The full arguments passed to the call. */
99
+ readonly arguments: unknown;
100
+ /** The full result envelope returned — every field, including `disclosures`. */
101
+ readonly result: unknown;
102
+ readonly durationMs: number;
103
+ /** No model calls exist yet in this repo — always `null` today. Never invented as 0. */
104
+ readonly cost: number | null;
105
+ }
106
+ export interface RecordCallOptions {
107
+ readonly now?: () => number;
108
+ /** The redaction seam — see this file's module header. */
109
+ readonly redact?: (call: ToolCallRecord) => ToolCallRecord;
110
+ }
111
+ export type RecordCallResult = {
112
+ readonly recorded: true;
113
+ readonly seq: number;
114
+ } | {
115
+ readonly recorded: false;
116
+ readonly error: unknown;
117
+ };
118
+ /** Record one call. Never throws — see this file's module header. */
119
+ export declare function recordCall(driver: SqlDriver, runId: string, call: ToolCallRecord, options?: RecordCallOptions): RecordCallResult;
120
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/recording/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAsB7G;AAED,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,cAAc,CAAC;CAC5D;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D,qEAAqE;AACrE,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAsClB"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Recording a run and its calls — `ai-tool-contract.md` §6, rule 5 (§41.15).
3
+ *
4
+ * ## Ordering is a counter, not a clock
5
+ *
6
+ * Two calls can land in the same millisecond, so `recorded_at` cannot be the
7
+ * ordering key. `recorded_runs.next_seq` is incremented atomically with every
8
+ * insert into `recorded_calls`, in the same transaction, so "what order did
9
+ * these happen in" is answerable from the primary key alone — the property
10
+ * §12's replay requirement depends on ("same inputs + same recorded responses
11
+ * = the same finding, byte for byte" only holds if a replayer can tell what
12
+ * order to feed them back in).
13
+ *
14
+ * ## `recordCall` cannot change the tool's own return
15
+ *
16
+ * A caller is expected to call the real tool, get its result, and *then* call
17
+ * `recordCall` — recording is telemetry about a call that has already
18
+ * happened, never a gate on it. So `recordCall` never throws: every failure
19
+ * inside it — no such run, a driver error, `arguments`/`result` containing
20
+ * something `canonicalJson` refuses (a `bigint`, a top-level `undefined`) — is
21
+ * caught here and reported back as `{ recorded: false, error }`. A caller that
22
+ * ignores the return value still gets its tool result untouched. This is
23
+ * `CLAUDE.md`'s honest-degradation rule applied to the recorder itself: a
24
+ * failed recording is disclosed in the return value, never silent, and never
25
+ * fatal to the call it was recording.
26
+ *
27
+ * ## Redaction is a documented seam
28
+ *
29
+ * Contract §6 puts redaction at the tool boundary, before the model call.
30
+ * `RecordCallOptions.redact`, when supplied, runs on the call *before* it is
31
+ * serialised, so nothing unredacted ever reaches `canonicalJson` once a
32
+ * redactor is passed in. `./redact.ts`'s `createRecordingRedactor` is a real
33
+ * implementation of that seam, ported from the model-client redaction seam in
34
+ * descry-desktop — see its module header. It is still opt-in rather than
35
+ * automatic here: this module has no way to know a deployment's actual
36
+ * secret values on its own, only the caller does, so a caller must construct
37
+ * the redactor and pass it. **No caller in this repo does that yet** — the
38
+ * MCP server's tool dispatch and `descry-runtime`'s do not call `recordCall`
39
+ * at all today (that wiring is separate, P3-boundary work) — so every row
40
+ * this module writes today is still exactly what the tool call carried. See
41
+ * `schema.ts`'s module header before treating a recording database file as
42
+ * safe to hand around.
43
+ */
44
+ import { randomUUID } from "node:crypto";
45
+ import { canonicalJson } from "@descryy/ir";
46
+ export function startRun(driver, stamp, options = {}) {
47
+ const now = options.now ?? Date.now;
48
+ const mintId = options.id ?? (() => `run1:${randomUUID()}`);
49
+ const runId = mintId();
50
+ driver
51
+ .prepare(`INSERT INTO recorded_runs
52
+ (id, started_at, commit_sha, built_at, ir_schema_version, prompt_version, model_id, next_seq)
53
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0)`)
54
+ .run(runId, now(), stamp.commitSha, stamp.builtAt, stamp.irSchemaVersion, options.promptVersion ?? null, options.modelId ?? null);
55
+ return { runId };
56
+ }
57
+ /** Record one call. Never throws — see this file's module header. */
58
+ export function recordCall(driver, runId, call, options = {}) {
59
+ try {
60
+ const now = options.now ?? Date.now;
61
+ return driver.transaction(() => {
62
+ const run = driver.prepare("SELECT next_seq FROM recorded_runs WHERE id = ?").get(runId);
63
+ if (run === undefined) {
64
+ throw new Error(`recordCall: no such run "${runId}" — startRun must be called first`);
65
+ }
66
+ const seq = run.next_seq;
67
+ const effective = options.redact ? options.redact(call) : call;
68
+ driver
69
+ .prepare(`INSERT INTO recorded_calls
70
+ (run_id, seq, tool, tool_version, class, arguments, result, duration_ms, cost, recorded_at)
71
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
72
+ .run(runId, seq, effective.tool, effective.toolVersion, effective.class, canonicalJson(effective.arguments), canonicalJson(effective.result), effective.durationMs, effective.cost, now());
73
+ driver.prepare("UPDATE recorded_runs SET next_seq = ? WHERE id = ?").run(seq + 1, runId);
74
+ return { recorded: true, seq };
75
+ });
76
+ }
77
+ catch (error) {
78
+ return { recorded: false, error };
79
+ }
80
+ }
81
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/recording/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAC;AAkD5D,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,KAAe,EAAE,UAA2B,EAAE;IACxF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,UAAU,EAAE,EAAE,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;IAEvB,MAAM;SACH,OAAO,CACN;;uCAEiC,CAClC;SACA,GAAG,CACF,KAAK,EACL,GAAG,EAAE,EACL,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,eAAe,EACrB,OAAO,CAAC,aAAa,IAAI,IAAI,EAC7B,OAAO,CAAC,OAAO,IAAI,IAAI,CACxB,CAAC;IAEJ,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC;AA0BD,qEAAqE;AACrE,MAAM,UAAU,UAAU,CACxB,MAAiB,EACjB,KAAa,EACb,IAAoB,EACpB,UAA6B,EAAE;IAE/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAEpC,OAAO,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAuB,iDAAiD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC/G,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,4BAA4B,KAAK,mCAAmC,CAAC,CAAC;YACxF,CAAC;YACD,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC;YACzB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAE/D,MAAM;iBACH,OAAO,CACN;;iDAEuC,CACxC;iBACA,GAAG,CACF,KAAK,EACL,GAAG,EACH,SAAS,CAAC,IAAI,EACd,SAAS,CAAC,WAAW,EACrB,SAAS,CAAC,KAAK,EACf,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,EAClC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,EAC/B,SAAS,CAAC,UAAU,EACpB,SAAS,CAAC,IAAI,EACd,GAAG,EAAE,CACN,CAAC;YAEJ,MAAM,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YAEzF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAW,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;AACH,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Fan-out attenuation — DEC-067.
3
+ *
4
+ * ## The problem this solves, and the one it must not create
5
+ *
6
+ * A handful of declarations are depended on by everything: on one measured
7
+ * repository `Stage` has 186 dependents and `DocumentType` 165. Those
8
+ * dependencies are **real**, and the graph represents them at full weight. The
9
+ * damage is not in the edge, it is in what traversal does when it *passes
10
+ * through* such a node — one function's reference to an enum drags 164
11
+ * unrelated consumers into a blast radius they have nothing to do with.
12
+ *
13
+ * So the attenuation attaches to **expansion out of a hub**, never to the edge.
14
+ * Weighting the edge would penalise both directions, and only one of them is
15
+ * wrong:
16
+ *
17
+ * - **change `DocumentType`** — all 165 dependents genuinely break. Damping that
18
+ * makes Descry *understate* real impact, which is the opposite of the failure
19
+ * being fixed.
20
+ * - **change a function that references `DocumentType`** — the other 164
21
+ * consumers are unaffected, and pulling them in is noise.
22
+ *
23
+ * **The seed exemption is what separates those two cases**, and without it this
24
+ * module contradicts its own reason for existing. Seeds enter traversal at depth
25
+ * 0; attenuation applies only from depth 1 onward. A hub that was *changed* is
26
+ * never attenuated; a hub that was *reached* always is.
27
+ *
28
+ * ## Fan-out is not stored anywhere
29
+ *
30
+ * It is derived at traversal time from the adjacency already fetched, and is
31
+ * never written to a node, never part of identity, never hashed, never in a
32
+ * qualified symbol path, and never persisted (DEC-017's derived/earned split
33
+ * puts it firmly on the derived side — in fact it is not even that, it is not
34
+ * stored at all).
35
+ *
36
+ * ## Why the candidate count and not a separate in-degree query
37
+ *
38
+ * Traversal has already enumerated exactly the edges it would follow out of the
39
+ * current node. That count **is** the operational fan-out — the number of ways
40
+ * this node can explode — and it needs no second query, no index and no
41
+ * post-build pass. Defining fan-out as "edges expansion would follow" rather
42
+ * than as abstract in-degree also makes it the quantity that actually causes the
43
+ * dilution, rather than a proxy for it.
44
+ *
45
+ * One consequence has to be stated: `perNodeLimit` caps that enumeration, so a
46
+ * limit at or below the top boundary would understate a hub's fan-out and
47
+ * under-attenuate it. `validateScopingConfig` refuses that combination rather
48
+ * than silently mis-bucketing.
49
+ *
50
+ * ## Calibration
51
+ *
52
+ * Every number here is **provisional** (DEC-067). They are starting values from
53
+ * a single repository, chosen so that all four buckets are occupied — measured
54
+ * at 8.5% / 22.0% / 28.5% / 41.0% of `USES_TYPE` edges — which is the minimum
55
+ * property that makes the table worth having. They are to be tuned from real
56
+ * traversal data, like every other weight in §12.
57
+ */
58
+ /** One band of the attenuation table. `upTo` is inclusive. */
59
+ export interface FanOutBucket {
60
+ /** Largest fan-out in this band. The final bucket uses `Infinity`. */
61
+ readonly upTo: number;
62
+ /** Multiplier applied to expansion out of a node in this band. */
63
+ readonly multiplier: number;
64
+ /** Human label, for explaining a score rather than for logic. */
65
+ readonly label: string;
66
+ }
67
+ /**
68
+ * DEC-067's provisional table.
69
+ *
70
+ * `1.0` for the first band matters: an ordinary node must be completely
71
+ * unaffected, so that introducing this feature cannot move any score in a graph
72
+ * without hubs.
73
+ */
74
+ export declare const DEFAULT_FANOUT_BUCKETS: readonly FanOutBucket[];
75
+ /** The bucket a fan-out falls in. Never undefined — the last bucket is open. */
76
+ export declare function bucketFor(fanOut: number, buckets?: readonly FanOutBucket[]): FanOutBucket;
77
+ /**
78
+ * The multiplier for expansion out of a node.
79
+ *
80
+ * `depth === 0` is the seed exemption and is checked here rather than at the
81
+ * call site, so the rule lives in one place and cannot be half-applied.
82
+ */
83
+ export declare function attenuationFor(fanOut: number, depth: number, buckets?: readonly FanOutBucket[]): number;
84
+ /** The largest multiplier any bucket can apply — what termination must clear. */
85
+ export declare function maxAttenuation(buckets: readonly FanOutBucket[]): number;
86
+ //# sourceMappingURL=fanout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../../src/scoping/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAKzD,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,SAAS,YAAY,EAA2B,GACxD,YAAY,CAMd;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,SAAS,YAAY,EAA2B,GACxD,MAAM,CAGR;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAEvE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Fan-out attenuation — DEC-067.
3
+ *
4
+ * ## The problem this solves, and the one it must not create
5
+ *
6
+ * A handful of declarations are depended on by everything: on one measured
7
+ * repository `Stage` has 186 dependents and `DocumentType` 165. Those
8
+ * dependencies are **real**, and the graph represents them at full weight. The
9
+ * damage is not in the edge, it is in what traversal does when it *passes
10
+ * through* such a node — one function's reference to an enum drags 164
11
+ * unrelated consumers into a blast radius they have nothing to do with.
12
+ *
13
+ * So the attenuation attaches to **expansion out of a hub**, never to the edge.
14
+ * Weighting the edge would penalise both directions, and only one of them is
15
+ * wrong:
16
+ *
17
+ * - **change `DocumentType`** — all 165 dependents genuinely break. Damping that
18
+ * makes Descry *understate* real impact, which is the opposite of the failure
19
+ * being fixed.
20
+ * - **change a function that references `DocumentType`** — the other 164
21
+ * consumers are unaffected, and pulling them in is noise.
22
+ *
23
+ * **The seed exemption is what separates those two cases**, and without it this
24
+ * module contradicts its own reason for existing. Seeds enter traversal at depth
25
+ * 0; attenuation applies only from depth 1 onward. A hub that was *changed* is
26
+ * never attenuated; a hub that was *reached* always is.
27
+ *
28
+ * ## Fan-out is not stored anywhere
29
+ *
30
+ * It is derived at traversal time from the adjacency already fetched, and is
31
+ * never written to a node, never part of identity, never hashed, never in a
32
+ * qualified symbol path, and never persisted (DEC-017's derived/earned split
33
+ * puts it firmly on the derived side — in fact it is not even that, it is not
34
+ * stored at all).
35
+ *
36
+ * ## Why the candidate count and not a separate in-degree query
37
+ *
38
+ * Traversal has already enumerated exactly the edges it would follow out of the
39
+ * current node. That count **is** the operational fan-out — the number of ways
40
+ * this node can explode — and it needs no second query, no index and no
41
+ * post-build pass. Defining fan-out as "edges expansion would follow" rather
42
+ * than as abstract in-degree also makes it the quantity that actually causes the
43
+ * dilution, rather than a proxy for it.
44
+ *
45
+ * One consequence has to be stated: `perNodeLimit` caps that enumeration, so a
46
+ * limit at or below the top boundary would understate a hub's fan-out and
47
+ * under-attenuate it. `validateScopingConfig` refuses that combination rather
48
+ * than silently mis-bucketing.
49
+ *
50
+ * ## Calibration
51
+ *
52
+ * Every number here is **provisional** (DEC-067). They are starting values from
53
+ * a single repository, chosen so that all four buckets are occupied — measured
54
+ * at 8.5% / 22.0% / 28.5% / 41.0% of `USES_TYPE` edges — which is the minimum
55
+ * property that makes the table worth having. They are to be tuned from real
56
+ * traversal data, like every other weight in §12.
57
+ */
58
+ /**
59
+ * DEC-067's provisional table.
60
+ *
61
+ * `1.0` for the first band matters: an ordinary node must be completely
62
+ * unaffected, so that introducing this feature cannot move any score in a graph
63
+ * without hubs.
64
+ */
65
+ export const DEFAULT_FANOUT_BUCKETS = [
66
+ { upTo: 5, multiplier: 1.0, label: "local" },
67
+ { upTo: 20, multiplier: 0.8, label: "shared" },
68
+ { upTo: 50, multiplier: 0.5, label: "high fan-out" },
69
+ { upTo: Number.POSITIVE_INFINITY, multiplier: 0.25, label: "hub" },
70
+ ];
71
+ /** The bucket a fan-out falls in. Never undefined — the last bucket is open. */
72
+ export function bucketFor(fanOut, buckets = DEFAULT_FANOUT_BUCKETS) {
73
+ for (const bucket of buckets) {
74
+ if (fanOut <= bucket.upTo)
75
+ return bucket;
76
+ }
77
+ // Unreachable when the table is validated, since the last `upTo` is Infinity.
78
+ return buckets[buckets.length - 1];
79
+ }
80
+ /**
81
+ * The multiplier for expansion out of a node.
82
+ *
83
+ * `depth === 0` is the seed exemption and is checked here rather than at the
84
+ * call site, so the rule lives in one place and cannot be half-applied.
85
+ */
86
+ export function attenuationFor(fanOut, depth, buckets = DEFAULT_FANOUT_BUCKETS) {
87
+ if (depth === 0)
88
+ return 1;
89
+ return bucketFor(fanOut, buckets).multiplier;
90
+ }
91
+ /** The largest multiplier any bucket can apply — what termination must clear. */
92
+ export function maxAttenuation(buckets) {
93
+ return buckets.reduce((largest, bucket) => Math.max(largest, bucket.multiplier), 0);
94
+ }
95
+ //# sourceMappingURL=fanout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fanout.js","sourceRoot":"","sources":["../../src/scoping/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAYH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAA4B;IAC7D,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE;IAC5C,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE;IAC9C,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE;IACpD,EAAE,IAAI,EAAE,MAAM,CAAC,iBAAiB,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;CACnE,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,SAAS,CACvB,MAAc,EACd,UAAmC,sBAAsB;IAEzD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI;YAAE,OAAO,MAAM,CAAC;IAC3C,CAAC;IACD,8EAA8E;IAC9E,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,KAAa,EACb,UAAmC,sBAAsB;IAEzD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC;AAC/C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,OAAgC;IAC7D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { DEFAULT_EDGE_WEIGHTS, DEFAULT_SCOPING_CONFIG, validateScopingConfig, ScopingConfigError, } from "./weights.ts";
2
+ export type { ScopingConfig } from "./weights.ts";
3
+ export { attenuationFor, bucketFor, maxAttenuation, DEFAULT_FANOUT_BUCKETS, } from "./fanout.ts";
4
+ export type { FanOutBucket } from "./fanout.ts";
5
+ export { HistoryFactors, hopDecay, finalScore, maxBoost } from "./score.ts";
6
+ export { NODE_BUDGETS, TIER_THRESHOLDS, tierFor } from "./tiers.ts";
7
+ export type { ScopeTier, Trigger } from "./tiers.ts";
8
+ export { scopeChange } from "./traverse.ts";
9
+ export type { Scope, ScopedNode, ScopeOptions } from "./traverse.ts";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scoping/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EACL,cAAc,EACd,SAAS,EACT,cAAc,EACd,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { DEFAULT_EDGE_WEIGHTS, DEFAULT_SCOPING_CONFIG, validateScopingConfig, ScopingConfigError, } from "./weights.js";
2
+ export { attenuationFor, bucketFor, maxAttenuation, DEFAULT_FANOUT_BUCKETS, } from "./fanout.js";
3
+ export { HistoryFactors, hopDecay, finalScore, maxBoost } from "./score.js";
4
+ export { NODE_BUDGETS, TIER_THRESHOLDS, tierFor } from "./tiers.js";
5
+ export { scopeChange } from "./traverse.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scoping/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,cAAc,EACd,SAAS,EACT,cAAc,EACd,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The impact score — §12.4.
3
+ *
4
+ * NewScore = ParentScore × EdgeWeight × ChangeCouplingFactor
5
+ * × IncidentCorrelationFactor × EdgeConfidence
6
+ *
7
+ * Both history factors **default to 1.0** so a brand-new file, or a repo with no
8
+ * logged incidents, is not unfairly under-scored. `EdgeConfidence` defaults to
9
+ * the edge's own value, which is 1.0 for a well-resolved adapter — so in a clean
10
+ * R3 repository the last three terms vanish and the formula is just weight
11
+ * decay. They do their work in a degraded one, which is the point.
12
+ *
13
+ * **The formula is applied in two parts, not as one product per hop.** Path
14
+ * decay uses only `EdgeWeight × EdgeConfidence`, which is strictly below 1 and
15
+ * therefore always terminates; the two history factors are applied once to a
16
+ * node's final score. See the long note in `weights.ts` for why the literal
17
+ * per-hop form cannot hold alongside §12.3 and §12.7, and DEC-037 for the
18
+ * decision. The short version: compounding a node's incident history at every
19
+ * hop of a path that merely passes through it counts the same evidence
20
+ * repeatedly.
21
+ */
22
+ import type { IREdge } from "@descryy/ir";
23
+ import type { AdjacencyProvider } from "../query/provider.ts";
24
+ import type { ScopingConfig } from "./weights.ts";
25
+ /**
26
+ * Per-node history factors, computed once and cached.
27
+ *
28
+ * Both are looked up on the node being *entered*, not on the edge: "this file
29
+ * has been involved in incidents" and "this file moves with the change" are
30
+ * properties of the destination, and computing them per edge would multiply the
31
+ * same boost in twice for a node reached two ways.
32
+ */
33
+ export declare class HistoryFactors {
34
+ #private;
35
+ constructor(provider: AdjacencyProvider, config: ScopingConfig, seeds: ReadonlySet<string>);
36
+ /**
37
+ * §12.4's `ChangeCouplingFactor`, from `CHANGES_WITH` edges (§11.7).
38
+ *
39
+ * Boosts a node that historically moves with something in the diff. Two things
40
+ * make this narrower than it first appears, and both are honest limits rather
41
+ * than bugs:
42
+ *
43
+ * - `CHANGES_WITH` is **file-to-file**, so this only fires for `FILE` nodes.
44
+ * A `FUNCTION` never has co-change history of its own.
45
+ * - The edge is stored in one canonical direction (DEC-036), so both
46
+ * directions must be read. This is the one edge type where `outgoing` alone
47
+ * is wrong.
48
+ */
49
+ couplingFor(nodeId: string): number;
50
+ /**
51
+ * §12.4's `IncidentCorrelationFactor`, from `INCIDENT_CORRELATED` edges (§11.6).
52
+ *
53
+ * Saturating rather than linear: the third incident on a file says much less
54
+ * than the first, and an unbounded count would let one pathological file
55
+ * dominate every blast radius it appears in.
56
+ */
57
+ incidentFor(nodeId: string): number;
58
+ }
59
+ /**
60
+ * The per-hop decay: `EdgeWeight × EdgeConfidence`, and nothing else.
61
+ *
62
+ * Strictly below 1 for any valid configuration, so the path score decreases
63
+ * monotonically and traversal terminates on any graph, cyclic or not. That is
64
+ * the whole termination argument, and it does not depend on what the history
65
+ * factors happen to be — which is what makes it a guarantee rather than a hope.
66
+ */
67
+ export declare function hopDecay(edge: IREdge, config: ScopingConfig): number;
68
+ /**
69
+ * A node's final score: its best path decay, adjusted once by its own history.
70
+ *
71
+ * Capped at `maxScore` because §12.7 tiers on absolute thresholds — a boosted
72
+ * score above 1.0 would put a two-hop node above the diff itself.
73
+ */
74
+ export declare function finalScore(pathScore: number, nodeId: string, config: ScopingConfig, history: HistoryFactors): number;
75
+ /**
76
+ * The largest amount any node's score can be raised by history.
77
+ *
78
+ * Pruning happens on the *unboosted* path score, so the prune point must be
79
+ * lowered by this much — otherwise a node that would clear the threshold once
80
+ * boosted is discarded before its boost is ever computed.
81
+ */
82
+ export declare function maxBoost(config: ScopingConfig): number;
83
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/scoping/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAY,MAAM,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;GAOG;AACH,qBAAa,cAAc;;gBAOb,QAAQ,EAAE,iBAAiB,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC;IAM1F;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAwBnC;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAUpC;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,GACtB,MAAM,CAGR;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAEtD"}