@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,212 @@
1
+ /**
2
+ * §14.6 — two open changes each adding a migration from the same parent, caught
3
+ * before the merge that turns it into a broken deploy.
4
+ *
5
+ * ## What this computes, and why it is the real computation rather than a proxy
6
+ *
7
+ * Migrations form a DAG: each revision names its parent(s), and a **head** is a
8
+ * revision nothing else descends from. Every migration runner refuses to run
9
+ * with more than one — Alembic's *"Multiple head revisions are present"* is the
10
+ * canonical spelling, and the equivalent exists in every tool that versions
11
+ * migrations at all.
12
+ *
13
+ * The obvious shortcut is to group migrations by declared parent and flag any
14
+ * parent claimed by two different changes. **That is wrong**, and wrong in the
15
+ * direction that produces false findings: a merge migration declaring two
16
+ * parents exists precisely to resolve such a fork, and the shortcut cannot see
17
+ * it. So this computes heads over the union the way the runner does. The check
18
+ * is only worth its Class A ceiling if it computes what the tool computes.
19
+ *
20
+ * ## Nothing here names a framework, and that is §14.6's own argument
21
+ *
22
+ * §14.6 says the SQL adapter makes this work identically across ORMs and raw-SQL
23
+ * tooling "rather than being written once per framework". So the input is a
24
+ * revision, its declared parents and where it came from — the shape every
25
+ * migration tool has, whatever it calls the fields. Reading `down_revision` out
26
+ * of an Alembic file, `dependencies` out of a Django one or a timestamp chain
27
+ * out of a Rails one is producer work. Rule 1: none of those words appear below
28
+ * this sentence.
29
+ *
30
+ * ## The class, and a disagreement with §15.2 that is already settled
31
+ *
32
+ * §15.2 lists "migration head conflict" as **Class A — deterministically
33
+ * verifiable, a structural fact, not an inference**. That is right about the
34
+ * *inference*: given both parent lists, the conclusion follows with no
35
+ * judgement at all. It is not right unconditionally, because the parent lists
36
+ * still have to be obtained, and reading `down_revision` with a regex is R0
37
+ * evidence however certain the arithmetic on top of it.
38
+ *
39
+ * This is the same disagreement the env/config check hit, and §15.3 already
40
+ * records the resolution: **the Class column is a ceiling, not a floor** —
41
+ * "corrected after the env/config check was built and the two disagreed". So A
42
+ * is what this check reaches when the records come from the framework's own
43
+ * migration history (DEC-115's promotion path, R3), and a source-read set lands
44
+ * where its evidence lands. DEC-058 at the finding level: the claim carries the
45
+ * resolution of its own evidence, not of the run.
46
+ *
47
+ * That is now the second check to disagree with §15.2's column in the same
48
+ * direction, which is worth someone's attention as a pattern rather than as two
49
+ * incidents.
50
+ *
51
+ * ## An unread migration is not a migration with no parents
52
+ *
53
+ * An initial migration genuinely has zero parents. A migration whose parent line
54
+ * could not be read has *unknown* parents. Both are "no parents in the record",
55
+ * and collapsing them silently corrupts the head computation — an unread
56
+ * migration might be exactly the merge that resolves the fork being reported.
57
+ *
58
+ * So `parents` is `readonly string[] | "unknown"` and the empty array cannot be
59
+ * used to mean the second thing. The distinction is made unrepresentable at the
60
+ * type rather than handled downstream, which is the repair
61
+ * `bench/lib/README.md` asks for at construction time rather than afterwards.
62
+ */
63
+ import { reliabilityCap, reliabilityNote } from "@descryy/ir";
64
+ /**
65
+ * Head conflicts across a set of in-flight changes.
66
+ *
67
+ * Pure over records: no store, no graph, no framework. The producer that reads
68
+ * migration files decides `resolution`; this function propagates it.
69
+ */
70
+ export function findMigrationHeadConflicts(records, options = {}) {
71
+ const notes = [];
72
+ const changes = new Set(records.map((r) => r.prId)).size;
73
+ if (records.length === 0) {
74
+ notes.push("No change in this set adds a migration, so no head computation was performed. That is " +
75
+ "not a finding that the migrations are consistent — there are none to be consistent.");
76
+ return {
77
+ conflict: null,
78
+ dependencies: [],
79
+ duplicateRevisions: [],
80
+ analysed: false,
81
+ complete: true,
82
+ checked: { changes, migrations: 0, withUnknownParents: 0, heads: 0 },
83
+ notes,
84
+ };
85
+ }
86
+ const unknown = records.filter((r) => r.parents === "unknown");
87
+ const known = records.filter((r) => r.parents !== "unknown");
88
+ const complete = unknown.length === 0;
89
+ // Two changes minting the same revision id is a defect on its own, and it also
90
+ // makes the DAG ill-formed — so it is detected before the heads are computed
91
+ // rather than being allowed to distort them.
92
+ const byRevision = new Map();
93
+ for (const r of records) {
94
+ const list = byRevision.get(r.revision);
95
+ if (list === undefined)
96
+ byRevision.set(r.revision, [r]);
97
+ else
98
+ list.push(r);
99
+ }
100
+ const duplicateRevisions = [...byRevision]
101
+ .filter(([, rs]) => new Set(rs.map((r) => r.prId)).size > 1)
102
+ .map(([revision, rs]) => ({ revision, prIds: [...new Set(rs.map((r) => r.prId))].sort() }));
103
+ // A revision is a head when nothing in the merged set declares it as a parent.
104
+ // Only revisions the set DECLARES are candidates: a parent that nothing here
105
+ // declares lives in the base history and has, by construction, at least one
106
+ // child — the migration that named it.
107
+ const claimedAsParent = new Set();
108
+ for (const r of known) {
109
+ for (const p of r.parents)
110
+ claimedAsParent.add(p);
111
+ }
112
+ const heads = known
113
+ .filter((r) => !claimedAsParent.has(r.revision))
114
+ .map((r) => ({ revision: r.revision, prId: r.prId, file: r.file }))
115
+ .sort((a, b) => a.revision.localeCompare(b.revision));
116
+ // The weakest record decides the class — the same pair rule overlap scoring
117
+ // uses, for the same reason: a claim resting on several observations cannot
118
+ // be asserted at the strength of its best one.
119
+ const weakest = known.reduce((min, r) => (r.resolution < min ? r.resolution : min), 4);
120
+ const evidenceClass = reliabilityCap(weakest, true);
121
+ // A revision identifier is a name, so the R2 carve-out applies — but a partial
122
+ // DAG makes the conclusion conditional on something unread, which no amount of
123
+ // resolution on the read records repairs.
124
+ const reliability = complete ? evidenceClass : "C";
125
+ let disclosure = reliabilityNote(weakest, true);
126
+ if (!complete) {
127
+ disclosure +=
128
+ ` ${unknown.length} migration(s) in this set had parents that could not be read, so the ` +
129
+ "revision graph is partial. One of them may be the merge that resolves this, which is " +
130
+ "why this is reported as a possibility rather than as the structural fact it would " +
131
+ "otherwise be.";
132
+ }
133
+ const conflict = heads.length > 1
134
+ ? {
135
+ prIds: [...new Set(heads.map((h) => h.prId))].sort(),
136
+ heads,
137
+ reliability,
138
+ disclosure,
139
+ }
140
+ : null;
141
+ // A migration whose parent only exists inside another open change. Not a
142
+ // conflict — the merged set still has one head — but merging in the wrong
143
+ // order leaves a revision pointing at a parent that is not there yet.
144
+ const revisionOwner = new Map();
145
+ for (const r of records)
146
+ if (!revisionOwner.has(r.revision))
147
+ revisionOwner.set(r.revision, r.prId);
148
+ const dependencies = [];
149
+ for (const r of known) {
150
+ for (const parent of r.parents) {
151
+ const owner = revisionOwner.get(parent);
152
+ if (owner === undefined || owner === r.prId)
153
+ continue;
154
+ dependencies.push({
155
+ prId: r.prId,
156
+ revision: r.revision,
157
+ dependsOnRevision: parent,
158
+ dependsOnPrId: owner,
159
+ reliability,
160
+ disclosure,
161
+ });
162
+ }
163
+ }
164
+ if (conflict !== null) {
165
+ notes.push(`Merging these ${conflict.prIds.length} change(s) leaves ${heads.length} migration heads. ` +
166
+ "A migration runner refuses to advance with more than one, so this fails at deploy " +
167
+ "time rather than at merge time — which is the point of computing it now.");
168
+ notes.push(disclosure);
169
+ }
170
+ if (!complete) {
171
+ notes.push(`${unknown.length} of ${records.length} migration(s) had unreadable parent revisions and ` +
172
+ `were excluded from the graph: ${unknown
173
+ .slice(0, 5)
174
+ .map((r) => r.file)
175
+ .join(", ")}${unknown.length > 5 ? ", …" : ""}. They were not treated as having no ` +
176
+ "parents — an initial migration and an unread one are different, and conflating them " +
177
+ "would silently change the head count.");
178
+ }
179
+ if (duplicateRevisions.length > 0) {
180
+ notes.push(`${duplicateRevisions.length} revision identifier(s) are declared by more than one change. ` +
181
+ "Whatever the head count says, two migrations cannot share an identifier.");
182
+ }
183
+ if (options.baseHead === undefined) {
184
+ notes.push("The base branch's current head was not supplied, so migrations branching from stale " +
185
+ "history were not checked. Only conflicts between the supplied changes were computed.");
186
+ }
187
+ else {
188
+ const stale = known.filter((r) => r.parents.length > 0 &&
189
+ !r.parents.includes(options.baseHead) &&
190
+ !r.parents.some((p) => revisionOwner.has(p)));
191
+ if (stale.length > 0) {
192
+ notes.push(`${stale.length} migration(s) descend from a revision that is neither the base head nor ` +
193
+ "added by any change in this set, so they were written against older history: " +
194
+ `${stale.map((r) => r.file).join(", ")}.`);
195
+ }
196
+ }
197
+ return {
198
+ conflict,
199
+ dependencies,
200
+ duplicateRevisions,
201
+ analysed: true,
202
+ complete,
203
+ checked: {
204
+ changes,
205
+ migrations: records.length,
206
+ withUnknownParents: unknown.length,
207
+ heads: heads.length,
208
+ },
209
+ notes,
210
+ };
211
+ }
212
+ //# sourceMappingURL=migration-heads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-heads.js","sourceRoot":"","sources":["../../src/multipr/migration-heads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * §14.3 — where two in-flight changes touch the same thing, and whether that
3
+ * matters.
4
+ *
5
+ * ## A shared node alone is not a conflict
6
+ *
7
+ * §14.3 says this outright and it is the whole design. At real team scale many
8
+ * pull requests incidentally touch the same low-importance code — a shared
9
+ * logging helper, a barrel file, a constants module. A tool that reports every
10
+ * shared node reports mostly noise, and a developer who has dismissed six
11
+ * incidental overlaps will dismiss the seventh without reading it.
12
+ *
13
+ * So severity is a product of *what* is shared, not *that* something is:
14
+ *
15
+ * score(node) = impact × NODE_TYPE_WEIGHT[type]
16
+ *
17
+ * A shared `API_ENDPOINT` is two changes to one contract. A shared `FUNCTION`
18
+ * in a utility module is two people editing a file. The weights are the only
19
+ * thing separating them and they are tuning values, labelled as such.
20
+ *
21
+ * ## Resolution is carried per node, and that is §14.2's one addition
22
+ *
23
+ * §14.2 adds `Resolution` to the persisted row with a reason: *so overlap
24
+ * severity is not computed across incomparable evidence*. If one change's node
25
+ * was resolved by a type checker and the other's by syntax alone, an overlap
26
+ * asserted at the stronger level claims more than half of its own evidence
27
+ * supports.
28
+ *
29
+ * The rule follows DEC-058 exactly — a claim carries the resolution of ITS OWN
30
+ * evidence, not of the run that produced it. Here the claim rests on two
31
+ * observations, so it carries **the weaker of the two**, and the finding's
32
+ * reliability class follows from that rather than from either side alone.
33
+ *
34
+ * ## What this deliberately does not do
35
+ *
36
+ * §14.4's mechanical-versus-semantic classification needs the actual diff lines
37
+ * at the shared node, which this module never sees. It reports the overlap and
38
+ * stops; classifying it — and the auto-apply path that is the single documented
39
+ * exception to approval-before-code-change — belongs where the diff is.
40
+ * Guessing "mechanical" without reading the lines would be the one place in
41
+ * this system where a wrong guess edits somebody's branch.
42
+ */
43
+ import type { NodeType, ResolutionLevel } from "@descryy/ir";
44
+ import type { ReliabilityClass } from "@descryy/ir";
45
+ /** How a change touched a node. §14.2's `DiffType`. */
46
+ export type DiffType = "added" | "modified" | "deleted";
47
+ /**
48
+ * One row of a change's scoped subgraph — §14.2's shape, without persistence.
49
+ *
50
+ * Defined here rather than in a store module so overlap can be computed and
51
+ * tested with no database, the same reason `AdjacencyProvider` exists.
52
+ */
53
+ export interface PrScopeEntry {
54
+ readonly nodeId: string;
55
+ readonly nodeType: NodeType;
56
+ /** Present when known; used only for reporting, never for identity. */
57
+ readonly file?: string | undefined;
58
+ /** §12.4's impact score, for this change. */
59
+ readonly impactScore: number;
60
+ /** §12.7's tier, for this change. */
61
+ readonly tier: number;
62
+ readonly diffType: DiffType;
63
+ /** The level the graph reached for this node **in this change's run**. */
64
+ readonly resolution: ResolutionLevel;
65
+ }
66
+ export interface PrScope {
67
+ /** Whatever identifies the change — a PR number, a branch, a ref. */
68
+ readonly prId: string;
69
+ /** Part of scope identity: node ids are repo-scoped, so scopes are too. */
70
+ readonly repo: string;
71
+ readonly entries: readonly PrScopeEntry[];
72
+ /** When this scope was computed, for §14.2's staleness handling. */
73
+ readonly computedAt: number;
74
+ /** The head commit of the change itself. Identifies it; never compares it. */
75
+ readonly headSha: string;
76
+ /**
77
+ * The commit **the graph** was at when this scope was computed.
78
+ *
79
+ * Not the same as `headSha`, and the distinction is load-bearing: two scopes
80
+ * are comparable only when this agrees. A single `baseSha` field stood here
81
+ * first, described as "the base commit it was computed against", which is
82
+ * ambiguous between the two and would have let a comparison key on the wrong
83
+ * one. §14.2's column comment is what caught it.
84
+ */
85
+ readonly graphCommitSha: string;
86
+ }
87
+ /** One node two changes both touch. */
88
+ export interface SharedNode {
89
+ readonly nodeId: string;
90
+ readonly nodeType: NodeType;
91
+ readonly file?: string | undefined;
92
+ /** `max` of the two impact scores — see `impactOfPair`. */
93
+ readonly impact: number;
94
+ readonly weight: number;
95
+ readonly score: number;
96
+ /** How each side touched it. Two deletions and two additions differ. */
97
+ readonly diffTypes: readonly [DiffType, DiffType];
98
+ /** The **weaker** of the two resolutions. */
99
+ readonly resolution: ResolutionLevel;
100
+ readonly reliability: ReliabilityClass;
101
+ /** True when the two sides disagree about how well this node was resolved. */
102
+ readonly resolutionMismatch: boolean;
103
+ }
104
+ export interface OverlapFinding {
105
+ readonly a: string;
106
+ readonly b: string;
107
+ /** Every shared node, including those below the threshold. */
108
+ readonly overlapSize: number;
109
+ /** Shared nodes at or above the threshold, strongest first. */
110
+ readonly surfaced: readonly SharedNode[];
111
+ /** Sum of surfaced scores — see the note on §14.3's formula. */
112
+ readonly conflictScore: number;
113
+ /** The sentence a report must carry with this finding. */
114
+ readonly disclosure: string;
115
+ }
116
+ /** A pair that was never compared, and why. Never silently absent. */
117
+ export interface IncomparablePair {
118
+ readonly a: string;
119
+ readonly b: string;
120
+ readonly reason: "different-graph" | "different-repo";
121
+ }
122
+ export interface OverlapReport {
123
+ readonly findings: readonly OverlapFinding[];
124
+ /** The count before `limit` capped it — P27's disclosed remainder: a structured field, not only a prose note. */
125
+ readonly findingsTotal: number;
126
+ readonly checked: {
127
+ readonly scopes: number;
128
+ readonly pairs: number;
129
+ /** Pairs sharing at least one node, before the threshold. */
130
+ readonly pairsOverlapping: number;
131
+ /** Shared nodes dropped for scoring below the threshold. */
132
+ readonly belowThreshold: number;
133
+ /** Pairs refused before comparison — see `incomparable`. */
134
+ readonly incomparable: number;
135
+ };
136
+ /**
137
+ * Pairs that could not be compared at all.
138
+ *
139
+ * **An incomparable pair and a non-conflicting pair produce the same empty
140
+ * overlap**, so they are separated here rather than left to be inferred from
141
+ * a count that does not distinguish them.
142
+ */
143
+ readonly incomparable: readonly IncomparablePair[];
144
+ readonly notes: readonly string[];
145
+ }
146
+ /**
147
+ * §14.3's `NodeTypeWeight`. **Tuning values, and labelled as such.**
148
+ *
149
+ * The architecture gives the shape and one worked contrast — a shared utility
150
+ * function scores low, a shared API contract scores high — and fixes no
151
+ * numbers. These are a defensible starting point and are expected to move once
152
+ * there is data. The ordering is the part that is argued:
153
+ *
154
+ * - **Contract surfaces highest.** `API_ENDPOINT` and `API_ROUTE` are the join
155
+ * between two services; two changes to one is the case where a merge compiles
156
+ * and the system breaks.
157
+ * - **Schema next.** A `DATABASE_TABLE` or `DATABASE_COLUMN` touched twice is
158
+ * two migrations against one object, which §14.6 checks separately and which
159
+ * fails at merge rather than at review.
160
+ * - **Types above callables.** Two changes to a `DTO` or `MODEL` shape reach
161
+ * every consumer; two changes to one `FUNCTION` reach its callers.
162
+ * - **`FILE` lowest of the real types.** File-level overlap is what every naive
163
+ * tool already reports and it is the noise this weighting exists to suppress.
164
+ *
165
+ * Exhaustive over the frozen vocabulary on purpose: `Record<NodeType, number>`
166
+ * means adding a node type is a compile error here rather than a silent zero,
167
+ * and a silent zero would make the new type invisible to conflict detection
168
+ * without any test failing.
169
+ */
170
+ export declare const NODE_TYPE_WEIGHTS: Readonly<Record<NodeType, number>>;
171
+ /** Default score below which a shared node is incidental rather than a conflict. */
172
+ export declare const DEFAULT_CONFLICT_THRESHOLD = 0.3;
173
+ export interface OverlapOptions {
174
+ readonly threshold?: number;
175
+ readonly weights?: Readonly<Record<NodeType, number>>;
176
+ /** Cap on findings, per rule 5. Truncation is reported. */
177
+ readonly limit?: number;
178
+ }
179
+ /**
180
+ * Overlaps between every pair of in-flight changes.
181
+ *
182
+ * Pure over scopes, so it needs no store and no graph. §14.2's persistence is a
183
+ * layer above this, not a prerequisite for it.
184
+ */
185
+ export declare function findOverlaps(scopes: readonly PrScope[], options?: OverlapOptions): OverlapReport;
186
+ //# sourceMappingURL=overlap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlap.d.ts","sourceRoot":"","sources":["../../src/multipr/overlap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * §14.3 — where two in-flight changes touch the same thing, and whether that
3
+ * matters.
4
+ *
5
+ * ## A shared node alone is not a conflict
6
+ *
7
+ * §14.3 says this outright and it is the whole design. At real team scale many
8
+ * pull requests incidentally touch the same low-importance code — a shared
9
+ * logging helper, a barrel file, a constants module. A tool that reports every
10
+ * shared node reports mostly noise, and a developer who has dismissed six
11
+ * incidental overlaps will dismiss the seventh without reading it.
12
+ *
13
+ * So severity is a product of *what* is shared, not *that* something is:
14
+ *
15
+ * score(node) = impact × NODE_TYPE_WEIGHT[type]
16
+ *
17
+ * A shared `API_ENDPOINT` is two changes to one contract. A shared `FUNCTION`
18
+ * in a utility module is two people editing a file. The weights are the only
19
+ * thing separating them and they are tuning values, labelled as such.
20
+ *
21
+ * ## Resolution is carried per node, and that is §14.2's one addition
22
+ *
23
+ * §14.2 adds `Resolution` to the persisted row with a reason: *so overlap
24
+ * severity is not computed across incomparable evidence*. If one change's node
25
+ * was resolved by a type checker and the other's by syntax alone, an overlap
26
+ * asserted at the stronger level claims more than half of its own evidence
27
+ * supports.
28
+ *
29
+ * The rule follows DEC-058 exactly — a claim carries the resolution of ITS OWN
30
+ * evidence, not of the run that produced it. Here the claim rests on two
31
+ * observations, so it carries **the weaker of the two**, and the finding's
32
+ * reliability class follows from that rather than from either side alone.
33
+ *
34
+ * ## What this deliberately does not do
35
+ *
36
+ * §14.4's mechanical-versus-semantic classification needs the actual diff lines
37
+ * at the shared node, which this module never sees. It reports the overlap and
38
+ * stops; classifying it — and the auto-apply path that is the single documented
39
+ * exception to approval-before-code-change — belongs where the diff is.
40
+ * Guessing "mechanical" without reading the lines would be the one place in
41
+ * this system where a wrong guess edits somebody's branch.
42
+ */
43
+ import { reliabilityCap, reliabilityNote } from "@descryy/ir";
44
+ /**
45
+ * §14.3's `NodeTypeWeight`. **Tuning values, and labelled as such.**
46
+ *
47
+ * The architecture gives the shape and one worked contrast — a shared utility
48
+ * function scores low, a shared API contract scores high — and fixes no
49
+ * numbers. These are a defensible starting point and are expected to move once
50
+ * there is data. The ordering is the part that is argued:
51
+ *
52
+ * - **Contract surfaces highest.** `API_ENDPOINT` and `API_ROUTE` are the join
53
+ * between two services; two changes to one is the case where a merge compiles
54
+ * and the system breaks.
55
+ * - **Schema next.** A `DATABASE_TABLE` or `DATABASE_COLUMN` touched twice is
56
+ * two migrations against one object, which §14.6 checks separately and which
57
+ * fails at merge rather than at review.
58
+ * - **Types above callables.** Two changes to a `DTO` or `MODEL` shape reach
59
+ * every consumer; two changes to one `FUNCTION` reach its callers.
60
+ * - **`FILE` lowest of the real types.** File-level overlap is what every naive
61
+ * tool already reports and it is the noise this weighting exists to suppress.
62
+ *
63
+ * Exhaustive over the frozen vocabulary on purpose: `Record<NodeType, number>`
64
+ * means adding a node type is a compile error here rather than a silent zero,
65
+ * and a silent zero would make the new type invisible to conflict detection
66
+ * without any test failing.
67
+ */
68
+ export const NODE_TYPE_WEIGHTS = {
69
+ API_ENDPOINT: 1.0,
70
+ API_ROUTE: 1.0,
71
+ DATABASE_COLUMN: 0.9,
72
+ DATABASE_TABLE: 0.9,
73
+ DTO: 0.8,
74
+ MODEL: 0.8,
75
+ CLASS: 0.6,
76
+ COMPONENT: 0.6,
77
+ PAGE: 0.5,
78
+ MODULE: 0.5,
79
+ FUNCTION: 0.4,
80
+ TEST_CASE: 0.3,
81
+ FILE: 0.2,
82
+ // History artefacts. Two changes cannot both "touch" an incident; if these
83
+ // ever appear in a diff scope something upstream is wrong, and a weight of
84
+ // zero would hide that rather than surface it.
85
+ INCIDENT: 0.1,
86
+ FIX_PATTERN: 0.1,
87
+ };
88
+ /** Default score below which a shared node is incidental rather than a conflict. */
89
+ export const DEFAULT_CONFLICT_THRESHOLD = 0.3;
90
+ /**
91
+ * The impact a shared node carries is the **higher** of the two.
92
+ *
93
+ * A node peripheral to one change and central to the other is a real conflict:
94
+ * somebody is about to have their central concern edited from underneath them.
95
+ * Taking the minimum would let a large blast radius be discounted by a
96
+ * one-line drive-by touching the same node, which is the case most worth
97
+ * catching.
98
+ */
99
+ function impactOfPair(a, b) {
100
+ return Math.max(a.impactScore, b.impactScore);
101
+ }
102
+ /**
103
+ * Overlaps between every pair of in-flight changes.
104
+ *
105
+ * Pure over scopes, so it needs no store and no graph. §14.2's persistence is a
106
+ * layer above this, not a prerequisite for it.
107
+ */
108
+ export function findOverlaps(scopes, options = {}) {
109
+ const threshold = options.threshold ?? DEFAULT_CONFLICT_THRESHOLD;
110
+ const weights = options.weights ?? NODE_TYPE_WEIGHTS;
111
+ const limit = options.limit ?? 10;
112
+ const notes = [];
113
+ const incomparable = [];
114
+ let belowThreshold = 0;
115
+ let pairsOverlapping = 0;
116
+ let pairs = 0;
117
+ const byId = scopes.map((s) => ({ scope: s, index: new Map(s.entries.map((e) => [e.nodeId, e])) }));
118
+ const findings = [];
119
+ for (let i = 0; i < byId.length; i += 1) {
120
+ for (let j = i + 1; j < byId.length; j += 1) {
121
+ pairs += 1;
122
+ const left = byId[i];
123
+ const right = byId[j];
124
+ // Refused BEFORE any node is compared. §14.2's schema puts the graph
125
+ // commit in the primary key so two graph states of one PR cannot
126
+ // overwrite each other, and warns that a reader ignoring it "will happily
127
+ // overlap a scope from last week with one from today and report a
128
+ // number". That warning is honoured here rather than in the store,
129
+ // because a caller constructing scopes by hand never touches a store —
130
+ // and because an incomparable pair returns the same empty overlap a
131
+ // genuinely non-conflicting one does.
132
+ const reason = left.scope.repo !== right.scope.repo
133
+ ? "different-repo"
134
+ : left.scope.graphCommitSha !== right.scope.graphCommitSha
135
+ ? "different-graph"
136
+ : null;
137
+ if (reason !== null) {
138
+ incomparable.push({ a: left.scope.prId, b: right.scope.prId, reason });
139
+ continue;
140
+ }
141
+ const shared = [];
142
+ let overlapSize = 0;
143
+ let mismatches = 0;
144
+ for (const [nodeId, a] of left.index) {
145
+ const b = right.index.get(nodeId);
146
+ if (b === undefined)
147
+ continue;
148
+ overlapSize += 1;
149
+ // DEC-058 at the pair level: the claim rests on two observations, so it
150
+ // carries the weaker. Asserting at the stronger would claim more than
151
+ // half its own evidence supports.
152
+ const resolution = Math.min(a.resolution, b.resolution);
153
+ const resolutionMismatch = a.resolution !== b.resolution;
154
+ if (resolutionMismatch)
155
+ mismatches += 1;
156
+ const impact = impactOfPair(a, b);
157
+ const weight = weights[a.nodeType];
158
+ const score = impact * weight;
159
+ if (score < threshold) {
160
+ belowThreshold += 1;
161
+ continue;
162
+ }
163
+ shared.push({
164
+ nodeId,
165
+ nodeType: a.nodeType,
166
+ file: a.file ?? b.file,
167
+ impact,
168
+ weight,
169
+ score,
170
+ diffTypes: [a.diffType, b.diffType],
171
+ resolution,
172
+ // An overlap is a claim about which symbols two changes both name —
173
+ // a name-level fact, not a claim about shapes. R2 therefore permits
174
+ // class A here where a shape claim on the same evidence would not.
175
+ reliability: reliabilityCap(resolution, true),
176
+ resolutionMismatch,
177
+ });
178
+ }
179
+ if (overlapSize > 0)
180
+ pairsOverlapping += 1;
181
+ if (shared.length === 0)
182
+ continue;
183
+ shared.sort((x, y) => y.score - x.score || x.nodeId.localeCompare(y.nodeId));
184
+ const weakest = shared.reduce((min, s) => (s.resolution < min ? s.resolution : min), 4);
185
+ let disclosure = reliabilityNote(weakest, true);
186
+ if (mismatches > 0) {
187
+ disclosure +=
188
+ ` ${mismatches} of the ${overlapSize} shared node(s) were resolved to different ` +
189
+ "levels by the two changes; each is reported at the weaker of the two, because an " +
190
+ "overlap asserted at the stronger level would claim more than half its evidence " +
191
+ "supports.";
192
+ }
193
+ findings.push({
194
+ a: left.scope.prId,
195
+ b: right.scope.prId,
196
+ overlapSize,
197
+ surfaced: shared,
198
+ // §14.3 writes `OverlapSize × ImpactScore × NodeTypeWeight`. Read
199
+ // literally that multiplies a pair-level count by two node-level
200
+ // quantities, which lets one shared trivial node scale with the size of
201
+ // an unrelated overlap. Summing the per-node scores keeps the same
202
+ // intent — more shared, and more important, scores higher — and stays
203
+ // monotonic in both. The divergence is deliberate and recorded rather
204
+ // than silently "interpreted".
205
+ conflictScore: shared.reduce((sum, s) => sum + s.score, 0),
206
+ disclosure,
207
+ });
208
+ }
209
+ }
210
+ findings.sort((x, y) => y.conflictScore - x.conflictScore || x.a.localeCompare(y.a));
211
+ const kept = findings.slice(0, limit);
212
+ if (findings.length > limit) {
213
+ notes.push(`${findings.length} overlapping pairs scored above the threshold and the ${kept.length} ` +
214
+ "highest are reported.");
215
+ }
216
+ if (belowThreshold > 0) {
217
+ notes.push(`${belowThreshold} shared node(s) scored below the conflict threshold and were not ` +
218
+ "surfaced. A shared node is not a conflict on its own — at team scale many changes " +
219
+ "incidentally touch the same low-importance code, and reporting all of it is how a " +
220
+ "reader learns to dismiss the report.");
221
+ }
222
+ if (scopes.length < 2) {
223
+ notes.push("Fewer than two scopes were supplied, so no pair could be compared. That is not a " +
224
+ "finding that nothing conflicts.");
225
+ }
226
+ const differentGraph = incomparable.filter((p) => p.reason === "different-graph").length;
227
+ if (differentGraph > 0) {
228
+ notes.push(`${differentGraph} pair(s) were computed against different graph states and were not ` +
229
+ "compared. A scope is only meaningful against the graph it was computed on; comparing " +
230
+ "across two answers a different question and returns a plausible answer to it. These " +
231
+ "pairs are neither conflicting nor clear — they are unexamined, and rescoping both " +
232
+ "against one graph is what would settle them.");
233
+ }
234
+ const differentRepo = incomparable.filter((p) => p.reason === "different-repo").length;
235
+ if (differentRepo > 0) {
236
+ notes.push(`${differentRepo} pair(s) belong to different repositories and were not compared. Node ` +
237
+ "identity is repo-scoped, so such a pair shares no node by construction and would " +
238
+ "report as conflict-free rather than as unexamined.");
239
+ }
240
+ return {
241
+ findings: kept,
242
+ findingsTotal: findings.length,
243
+ checked: {
244
+ scopes: scopes.length,
245
+ pairs,
246
+ pairsOverlapping,
247
+ belowThreshold,
248
+ incomparable: incomparable.length,
249
+ },
250
+ incomparable,
251
+ notes,
252
+ };
253
+ }
254
+ //# sourceMappingURL=overlap.js.map