@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,302 @@
1
+ /**
2
+ * §15.3's rollback-readiness check — *"pattern-match migrations against a known
3
+ * list of destructive operations"*.
4
+ *
5
+ * Class A at R0, and **deliberately the easy half of consumer impact**: a
6
+ * destructive operation is a pattern in a migration, not an inference about who
7
+ * reads the column. The scope fence that makes that classification safe is stated
8
+ * in `migrations.ts` and every sentence below stays behind it.
9
+ *
10
+ * ## The trap, measured before this was written
11
+ *
12
+ * The env check's trap was a variable read and never declared. This one is
13
+ * larger, and DEC-122 counted it:
14
+ *
15
+ * | repository | migrations | with a reverse section |
16
+ * | --- | --- | --- |
17
+ * | `outline` | 300 | **300** |
18
+ * | `dispatch` | 162 | **162** |
19
+ * | `directus` | 112 | 110 |
20
+ *
21
+ * **Effectively every migration in these projects declares a reverse, and a
22
+ * destructive operation in the reverse is supposed to be there** — it undoes the
23
+ * forward migration. A check that matched destructive operations without knowing
24
+ * which half it was reading would report `outline`'s 202 destructive files, and
25
+ * most of those matches are a drop that undoes a create three lines above.
26
+ *
27
+ * So `section` is to this check what `required` was to the env check: the field
28
+ * without which the whole thing is wrong on nearly every file it sees. And
29
+ * `unknown` is not a synonym for `forward`. Two of the reference repositories
30
+ * declare no direction at all — they hand the framework a reversal callback — and
31
+ * their operations are **counted and disclosed, never reported**. That refusal
32
+ * alone withholds 192 files' worth of matches, and it is the honest treatment:
33
+ * an operation whose half cannot be determined is not evidence of a forward
34
+ * destructive change.
35
+ *
36
+ * ## Two further refusals, both from the only real DDL in the corpus
37
+ *
38
+ * - **`DROP … IF EXISTS` paired with a `CREATE` of the same object** is an
39
+ * idempotent setup script, not a data-losing change. Both destructive lines in
40
+ * the one genuine `.sql` file found across fifteen repositories are exactly
41
+ * this shape.
42
+ * - **Dropping an object created in the same migration** is not a finding. The
43
+ * object did not exist before the file ran.
44
+ *
45
+ * ## Where the input comes from
46
+ *
47
+ * Two sources, and DEC-122 measured that the one the architecture implies is the
48
+ * smaller: **0 `.sql` files inside a migration directory** across the reference
49
+ * corpus, against **3,301** migrations written as program code. The format half
50
+ * runs today and needs no adapter; the code half arrives as `attrs.migrationOps`
51
+ * and is an adapter's to produce. When neither is present the check says so and
52
+ * does not report a clean result.
53
+ */
54
+ import { fileScopedNodes } from "./nodes.js";
55
+ const DEFAULT_MAX_FINDINGS = 25;
56
+ /** DDL read straight from the repository needs no resolution — see `env.ts`. */
57
+ const DECLARATIVE_RESOLUTION = 3;
58
+ const SECTIONS = new Set(["forward", "reverse", "unknown"]);
59
+ const KINDS = new Set([
60
+ "DROP_TABLE",
61
+ "DROP_COLUMN",
62
+ "DROP_SCHEMA",
63
+ "TRUNCATE",
64
+ "RENAME",
65
+ ]);
66
+ /**
67
+ * Pull `attrs.migrationOps` off a file node, refusing anything malformed.
68
+ *
69
+ * A row with no `section`, or an unrecognised one, is **dropped rather than
70
+ * treated as forward**. Defaulting it would manufacture exactly the claim the
71
+ * reverse-section fence exists to withhold, on a corpus where the reverse
72
+ * section is present in effectively every file.
73
+ */
74
+ export function readMigrationOps(attrs) {
75
+ const raw = attrs["migrationOps"];
76
+ if (!Array.isArray(raw))
77
+ return { ops: [], malformed: 0 };
78
+ const ops = [];
79
+ let malformed = 0;
80
+ for (const entry of raw) {
81
+ if (typeof entry !== "object" || entry === null) {
82
+ malformed += 1;
83
+ continue;
84
+ }
85
+ const row = entry;
86
+ const kind = row["kind"];
87
+ const target = row["target"];
88
+ const section = row["section"];
89
+ const line = row["line"];
90
+ if (typeof kind !== "string" ||
91
+ !KINDS.has(kind) ||
92
+ typeof target !== "string" ||
93
+ target === "" ||
94
+ typeof section !== "string" ||
95
+ !SECTIONS.has(section)) {
96
+ malformed += 1;
97
+ continue;
98
+ }
99
+ ops.push({
100
+ kind: kind,
101
+ target,
102
+ section: section,
103
+ line: typeof line === "number" && Number.isFinite(line) ? line : 0,
104
+ guarded: row["guarded"] === true,
105
+ });
106
+ }
107
+ return { ops, malformed };
108
+ }
109
+ export function checkRollbackReadiness(provider, scan, options = {}) {
110
+ const maxFindings = options.maxFindings ?? DEFAULT_MAX_FINDINGS;
111
+ const scoped = options.files === undefined ? null : new Set(options.files);
112
+ const notes = [];
113
+ const candidates = [];
114
+ // --- the format half: DDL read from the repository -----------------------
115
+ let fromSource = 0;
116
+ for (const migration of scan.files) {
117
+ if (scoped !== null && !scoped.has(migration.file))
118
+ continue;
119
+ fromSource += 1;
120
+ for (const op of migration.ops) {
121
+ candidates.push({
122
+ op,
123
+ file: migration.file,
124
+ creates: migration.creates,
125
+ resolution: DECLARATIVE_RESOLUTION,
126
+ });
127
+ }
128
+ }
129
+ // --- the code half: whatever an adapter attached -------------------------
130
+ const nodes = fileScopedNodes(provider, options.limit);
131
+ let fromGraph = 0;
132
+ let malformed = 0;
133
+ for (const node of nodes.nodes) {
134
+ const path = node.file;
135
+ if (path === null)
136
+ continue;
137
+ if (scoped !== null && !scoped.has(path))
138
+ continue;
139
+ const extracted = readMigrationOps(node.attrs);
140
+ malformed += extracted.malformed;
141
+ if (extracted.ops.length === 0)
142
+ continue;
143
+ fromGraph += 1;
144
+ for (const op of extracted.ops) {
145
+ candidates.push({ op, file: path, creates: [], resolution: node.resolution });
146
+ }
147
+ }
148
+ // --- the refusals --------------------------------------------------------
149
+ const findings = [];
150
+ const withheld = { reverse: 0, undirected: 0, idempotent: 0, selfCreated: 0 };
151
+ for (const candidate of candidates) {
152
+ const { op } = candidate;
153
+ if (op.section === "reverse") {
154
+ withheld.reverse += 1;
155
+ continue;
156
+ }
157
+ if (op.section === "unknown") {
158
+ withheld.undirected += 1;
159
+ continue;
160
+ }
161
+ // Order separates two different refusals. A create *before* the drop means a
162
+ // temporary object that did not exist when the file started. A create
163
+ // *after* a guarded drop is an idempotent setup script. Without the line
164
+ // these collapse into one bucket and the disclosure names the wrong cause.
165
+ const object = op.target.split(".")[0] ?? op.target;
166
+ const matches = candidate.creates.filter((c) => c.name === op.target || c.name === object);
167
+ if (matches.some((c) => c.line < op.line)) {
168
+ withheld.selfCreated += 1;
169
+ continue;
170
+ }
171
+ if (op.guarded && matches.some((c) => c.line > op.line)) {
172
+ withheld.idempotent += 1;
173
+ continue;
174
+ }
175
+ findings.push({
176
+ kind: op.kind,
177
+ target: op.target,
178
+ file: candidate.file,
179
+ line: op.line,
180
+ detail: describe(op, candidate.file),
181
+ resolution: candidate.resolution,
182
+ });
183
+ }
184
+ // Better evidence first, then by file so two runs agree.
185
+ findings.sort((a, b) => b.resolution - a.resolution ||
186
+ a.file.localeCompare(b.file) ||
187
+ a.line - b.line ||
188
+ a.target.localeCompare(b.target));
189
+ const kept = findings.slice(0, maxFindings);
190
+ // --- disclosures ---------------------------------------------------------
191
+ const ran = fromSource > 0 || fromGraph > 0;
192
+ const blockers = [];
193
+ if (!ran) {
194
+ blockers.push("No migration was read. This check reads DDL files under a migration directory straight " +
195
+ "from the repository, and migration operations from attrs.migrationOps on a file-scoped node — " +
196
+ "an array of { kind, target, line, section }. Neither was present on this run. Migrations " +
197
+ "written as program code need a language adapter to emit that key — not every adapter does, " +
198
+ "and this run's did not. This is not a result of zero destructive operations.");
199
+ for (const blocker of blockers)
200
+ notes.push(blocker);
201
+ }
202
+ if (withheld.reverse > 0) {
203
+ notes.push(`${withheld.reverse} destructive operation(s) sit in a migration's reverse section and are ` +
204
+ "not reported. A drop there undoes the forward migration and is supposed to be present; " +
205
+ "reporting it would flag correct code in almost every migration in a typical project.");
206
+ }
207
+ if (withheld.undirected > 0) {
208
+ notes.push(`${withheld.undirected} destructive operation(s) are in migrations that declare no forward ` +
209
+ "or reverse section, so which half they belong to could not be determined. They are not " +
210
+ 'reported: an operation whose direction is unknown is not evidence of a forward ' +
211
+ "destructive change. This is the largest gap in this check and it closes when the " +
212
+ "producer records the section.");
213
+ }
214
+ if (withheld.selfCreated > 0) {
215
+ notes.push(`${withheld.selfCreated} operation(s) drop an object the same migration creates, and are ` +
216
+ "not reported: the object did not exist before the file ran.");
217
+ }
218
+ if (withheld.idempotent > 0) {
219
+ notes.push(`${withheld.idempotent} operation(s) are guarded by IF EXISTS in a file that also creates ` +
220
+ "objects — an idempotent setup script rather than an intentional change. Not reported.");
221
+ }
222
+ if (malformed > 0) {
223
+ notes.push(`${malformed} migration operation record(s) were dropped as malformed — an unrecognised ` +
224
+ 'kind, a missing target, or a missing or unknown "section". A row without a section is ' +
225
+ "dropped rather than assumed to be forward, because that assumption is what the reverse " +
226
+ "fence exists to prevent.");
227
+ }
228
+ if (scan.unparsed > 0) {
229
+ notes.push(`${scan.unparsed} file(s) under a migration directory held no statement this check ` +
230
+ "recognises. They were read and understood to contain nothing destructive, which is not " +
231
+ "the same as being verified safe.");
232
+ }
233
+ if (scan.unreadable.length > 0) {
234
+ notes.push(`${scan.unreadable.length} migration path(s) could not be read.`);
235
+ }
236
+ if (kept.length < findings.length) {
237
+ notes.push(`${findings.length - kept.length} further finding(s) were withheld: a report is capped at ` +
238
+ `${maxFindings} so a short list stays worth reading. They were found, not missed.`);
239
+ }
240
+ if (nodes.truncated) {
241
+ notes.push("More files exist in the graph than were enumerated, so the code half covers a prefix of " +
242
+ "the repository rather than all of it.");
243
+ }
244
+ if (scan.truncated) {
245
+ notes.push("The migration scan hit its file budget before finishing, so some migrations were not read.");
246
+ }
247
+ if (ran && scoped === null && findings.length > 0) {
248
+ // §15.1 runs this against the *scoped subgraph*. Unscoped, it reads every
249
+ // migration the project has ever written, so a long list here is the history
250
+ // of the repository rather than a property of a change. Measured: 33 and 67
251
+ // forward operations on two repositories, all correct and almost all years
252
+ // old.
253
+ notes.push(`${findings.length} operation(s) were found across the whole repository, which is every ` +
254
+ "migration the project has ever written rather than anything about a particular change. " +
255
+ 'This check is meant to run against a scoped change — pass "files" — where a handful of ' +
256
+ "migrations are in play. Read this list as history, not as a to-do.");
257
+ }
258
+ if (ran) {
259
+ notes.push("This check reports that a destructive operation is present. It does not assess whether the " +
260
+ "migration is safe to roll back — that needs the consumer-impact analysis this check is " +
261
+ "deliberately separate from.");
262
+ }
263
+ let resolutionFloor = DECLARATIVE_RESOLUTION;
264
+ for (const finding of kept) {
265
+ if (finding.resolution < resolutionFloor)
266
+ resolutionFloor = finding.resolution;
267
+ }
268
+ const stamp = { ...provider.baseStamp(), resolutionFloor };
269
+ return {
270
+ data: {
271
+ findings: kept,
272
+ findingsTotal: findings.length,
273
+ examined: { fromSource, fromGraph, operations: candidates.length },
274
+ withheld,
275
+ ran,
276
+ blockers,
277
+ },
278
+ stamp,
279
+ truncated: nodes.truncated || scan.truncated || kept.length < findings.length,
280
+ notes,
281
+ };
282
+ }
283
+ /** The sentence a developer reads. Behind the fence, always. */
284
+ function describe(op, file) {
285
+ const where = op.line > 0 ? `${file}:${op.line}` : file;
286
+ switch (op.kind) {
287
+ case "DROP_TABLE":
288
+ return `${where} drops the table ${op.target} in the forward migration.`;
289
+ case "DROP_COLUMN":
290
+ return `${where} drops the column ${op.target} in the forward migration.`;
291
+ case "DROP_SCHEMA":
292
+ return `${where} drops the schema ${op.target} in the forward migration.`;
293
+ case "TRUNCATE":
294
+ return `${where} truncates ${op.target} in the forward migration.`;
295
+ default:
296
+ // A rename is a different claim from a drop and is worded as one: the rows
297
+ // survive, and what breaks is every reader of the old name.
298
+ return (`${where} renames ${op.target} in the forward migration. The data is not destroyed; ` +
299
+ "anything still reading the old name stops finding it.");
300
+ }
301
+ }
302
+ //# sourceMappingURL=rollback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollback.js","sourceRoot":"","sources":["../../src/validation/rollback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Route drift — static extraction against framework introspection (DEC-115).
3
+ *
4
+ * The comparison logic only; identity from `@descryy/ir`, never re-implemented
5
+ * here. The `bench/route-drift.mjs` instrument does the harder half of this
6
+ * row — reading `php artisan route:list`, the `--introspection` file
7
+ * fallback, and the refusal to run an installer on a cloned repository (§5
8
+ * rule 10, DEC-115 clause 3). This module is the query it calls: given two
9
+ * already-extracted route lists, decide what they say about each other.
10
+ *
11
+ * ## The rule this file is built around, before anything else
12
+ *
13
+ * **A diff is the one measurement where an empty input produces the most
14
+ * reassuring possible output.** If introspection returns nothing —
15
+ * `artisan` not runnable, the application not bootable, the command failing
16
+ * quietly — the drift is zero, and zero drift is exactly what a healthy
17
+ * repository looks like. So both population *sizes* are part of the result,
18
+ * not scaffolding, and this module cannot report agreement it was not
19
+ * actually given. An empty or implausible side is a **refusal** to report
20
+ * drift, never a clean bill — the same shape this lane measured directly on
21
+ * `env-precision-adapter.mjs`: two runs both printed `decision accuracy
22
+ * 100.0%`, one on 5 decisions and one on 51, indistinguishable except for the
23
+ * confidence interval underneath. A diff has no interval to fall back on, so
24
+ * the refusal has to be explicit.
25
+ *
26
+ * ## The polarity is DEC-115's and is not re-opened here
27
+ *
28
+ * Static extraction is the **floor**; introspection is a **promotion** over
29
+ * the same node ids. The two disagreement directions are two findings with
30
+ * different audiences and different truth conditions, and collapsing them is
31
+ * the error:
32
+ *
33
+ * | direction | what it is | what it is NOT |
34
+ * | --- | --- | --- |
35
+ * | static says yes, listing says no | "these two sources disagree" | **never** "dead route" — ambiguous between the customer's app and our own false positive, and nothing here separates them |
36
+ * | listing says yes, static says no | a statement about **us**: the extractor missed one | not a finding about the customer at all |
37
+ *
38
+ * The second row is the more valuable one. Where a listing exists it is
39
+ * **ground truth for recall** — a complete denominator this project has
40
+ * never had without hand-building it.
41
+ */
42
+ /** One route, from either side, before identity is applied. */
43
+ export interface DriftRoute {
44
+ readonly method: string;
45
+ readonly path: string;
46
+ /** Repo-relative, when the side has one (the static extractor always does). */
47
+ readonly file?: string;
48
+ }
49
+ /**
50
+ * What the introspected side actually returned.
51
+ *
52
+ * Three refusal states plus `read`, mirroring `RollbackReport.ran` /
53
+ * `blockers` one level down: a caller needs to know *why* nothing was
54
+ * compared, not just that nothing was.
55
+ */
56
+ export type Introspection = {
57
+ readonly state: "read";
58
+ readonly source: string;
59
+ readonly routes: readonly DriftRoute[];
60
+ } | {
61
+ readonly state: "not-applicable";
62
+ readonly why: string;
63
+ } | {
64
+ readonly state: "refused";
65
+ readonly why: string;
66
+ } | {
67
+ readonly state: "unreadable";
68
+ readonly why: string;
69
+ };
70
+ export interface DriftReport {
71
+ readonly reported: boolean;
72
+ readonly staticCount: number;
73
+ readonly introspectedCount: number;
74
+ /** Present only when `reported` — see the refusal reasons on `Introspection` otherwise. */
75
+ readonly refusalReason?: string;
76
+ readonly staticOnly?: readonly {
77
+ readonly key: string;
78
+ readonly route: DriftRoute;
79
+ }[];
80
+ readonly listedOnly?: readonly {
81
+ readonly key: string;
82
+ readonly route: DriftRoute;
83
+ }[];
84
+ readonly agreed?: number;
85
+ /** `agreed / introspected`, when introspection is the ground truth for recall. */
86
+ readonly recallAgainstListing?: number;
87
+ }
88
+ export interface DriftOptions {
89
+ /**
90
+ * Below this, a listing is not believed.
91
+ *
92
+ * A Laravel application with `artisan` and one route is far more likely to
93
+ * be a listing that failed than an application with one route. Crude on
94
+ * purpose — it exists to catch "the command produced almost nothing," not
95
+ * to model route counts.
96
+ */
97
+ readonly implausibleBelow?: number;
98
+ }
99
+ /**
100
+ * The comparison, and the refusal that guards it.
101
+ *
102
+ * `staticOnly`/`listedOnly`/`agreed` are only ever populated when both sides
103
+ * were actually read and the introspected side is not implausibly small.
104
+ * Every other path returns `reported: false` carrying both counts, because
105
+ * the counts are the evidence the refusal was necessary.
106
+ */
107
+ export declare function checkRouteDrift(staticSide: readonly DriftRoute[], introspected: Introspection, options?: DriftOptions): DriftReport;
108
+ //# sourceMappingURL=route-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-drift.d.ts","sourceRoot":"","sources":["../../src/validation/route-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE,GAC3F;IAAE,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,2FAA2F;IAC3F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AASD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,UAAU,EAAE,EACjC,YAAY,EAAE,aAAa,EAC3B,OAAO,GAAE,YAAiB,GACzB,WAAW,CAkDb"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Route drift — static extraction against framework introspection (DEC-115).
3
+ *
4
+ * The comparison logic only; identity from `@descryy/ir`, never re-implemented
5
+ * here. The `bench/route-drift.mjs` instrument does the harder half of this
6
+ * row — reading `php artisan route:list`, the `--introspection` file
7
+ * fallback, and the refusal to run an installer on a cloned repository (§5
8
+ * rule 10, DEC-115 clause 3). This module is the query it calls: given two
9
+ * already-extracted route lists, decide what they say about each other.
10
+ *
11
+ * ## The rule this file is built around, before anything else
12
+ *
13
+ * **A diff is the one measurement where an empty input produces the most
14
+ * reassuring possible output.** If introspection returns nothing —
15
+ * `artisan` not runnable, the application not bootable, the command failing
16
+ * quietly — the drift is zero, and zero drift is exactly what a healthy
17
+ * repository looks like. So both population *sizes* are part of the result,
18
+ * not scaffolding, and this module cannot report agreement it was not
19
+ * actually given. An empty or implausible side is a **refusal** to report
20
+ * drift, never a clean bill — the same shape this lane measured directly on
21
+ * `env-precision-adapter.mjs`: two runs both printed `decision accuracy
22
+ * 100.0%`, one on 5 decisions and one on 51, indistinguishable except for the
23
+ * confidence interval underneath. A diff has no interval to fall back on, so
24
+ * the refusal has to be explicit.
25
+ *
26
+ * ## The polarity is DEC-115's and is not re-opened here
27
+ *
28
+ * Static extraction is the **floor**; introspection is a **promotion** over
29
+ * the same node ids. The two disagreement directions are two findings with
30
+ * different audiences and different truth conditions, and collapsing them is
31
+ * the error:
32
+ *
33
+ * | direction | what it is | what it is NOT |
34
+ * | --- | --- | --- |
35
+ * | static says yes, listing says no | "these two sources disagree" | **never** "dead route" — ambiguous between the customer's app and our own false positive, and nothing here separates them |
36
+ * | listing says yes, static says no | a statement about **us**: the extractor missed one | not a finding about the customer at all |
37
+ *
38
+ * The second row is the more valuable one. Where a listing exists it is
39
+ * **ground truth for recall** — a complete denominator this project has
40
+ * never had without hand-building it.
41
+ */
42
+ import { endpointQsp, normaliseEndpointPath } from "@descryy/ir";
43
+ const DEFAULT_IMPLAUSIBLE_BELOW = 3;
44
+ /** Both sides reduce to this, through the IR's own identity. Never re-implemented. */
45
+ function key(method, path) {
46
+ return endpointQsp(String(method || "").toUpperCase(), normaliseEndpointPath(String(path || "")));
47
+ }
48
+ /**
49
+ * The comparison, and the refusal that guards it.
50
+ *
51
+ * `staticOnly`/`listedOnly`/`agreed` are only ever populated when both sides
52
+ * were actually read and the introspected side is not implausibly small.
53
+ * Every other path returns `reported: false` carrying both counts, because
54
+ * the counts are the evidence the refusal was necessary.
55
+ */
56
+ export function checkRouteDrift(staticSide, introspected, options = {}) {
57
+ const implausibleBelow = options.implausibleBelow ?? DEFAULT_IMPLAUSIBLE_BELOW;
58
+ if (introspected.state !== "read") {
59
+ return {
60
+ reported: false,
61
+ staticCount: staticSide.length,
62
+ introspectedCount: 0,
63
+ refusalReason: `introspection ${introspected.state}: ${introspected.why}`,
64
+ };
65
+ }
66
+ if (introspected.routes.length < implausibleBelow) {
67
+ return {
68
+ reported: false,
69
+ staticCount: staticSide.length,
70
+ introspectedCount: introspected.routes.length,
71
+ refusalReason: `the listing has fewer than ${implausibleBelow} routes — more likely a failed capture than ` +
72
+ `a genuinely small application.`,
73
+ };
74
+ }
75
+ const staticKeys = new Map();
76
+ for (const route of staticSide)
77
+ staticKeys.set(key(route.method, route.path), route);
78
+ const listedKeys = new Map();
79
+ for (const route of introspected.routes)
80
+ listedKeys.set(key(route.method, route.path), route);
81
+ const staticOnly = [...staticKeys]
82
+ .filter(([k]) => !listedKeys.has(k))
83
+ .map(([k, route]) => ({ key: k, route }));
84
+ const listedOnly = [...listedKeys]
85
+ .filter(([k]) => !staticKeys.has(k))
86
+ .map(([k, route]) => ({ key: k, route }));
87
+ const agreed = [...staticKeys].filter(([k]) => listedKeys.has(k)).length;
88
+ return {
89
+ reported: true,
90
+ staticCount: staticSide.length,
91
+ introspectedCount: introspected.routes.length,
92
+ staticOnly,
93
+ listedOnly,
94
+ agreed,
95
+ // `exactOptionalPropertyTypes` distinguishes an absent property from one
96
+ // present-and-undefined, so the spread includes the key only when there
97
+ // is a real ratio — `listedKeys.size` is never 0 on this path, since
98
+ // `implausibleBelow` already refused anything under it, but the type
99
+ // stays honest about the precondition rather than asserting past it.
100
+ ...(listedKeys.size === 0 ? {} : { recallAgainstListing: agreed / listedKeys.size }),
101
+ };
102
+ }
103
+ //# sourceMappingURL=route-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-drift.js","sourceRoot":"","sources":["../../src/validation/route-drift.ts"],"names":[],"mappings":"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"}