@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,124 @@
1
+ /**
2
+ * TIER-A-2's value-lookup half — DEC-223, DEC-136.
3
+ *
4
+ * DEC-120/DEC-121 (`validation/{env,sources}.ts`, Lane D) already answer *"is
5
+ * this variable declared anywhere"*. A refusal-ledger row needs a different
6
+ * question answered: *"what value does this blocked blockedBy hold, per
7
+ * environment, from what the repository already has on disk."* Same
8
+ * declaration surfaces, a different consumer — this file only reads
9
+ * `sources.ts`'s exports, it does not change them. See `parallel-agents.md`
10
+ * §3's ledger-lookup row.
11
+ *
12
+ * ## What this deliberately does not take as input, and why
13
+ *
14
+ * The obvious signature would be `resolve(row: UnresolvedRef)`. `UnresolvedRef`
15
+ * does not carry the blocked blockedBy — `rawTarget` is the callee
16
+ * (`"fetch"`, `"api.get"`), and `reason` is human prose with no variable name
17
+ * in it (confirmed by reading real rows: the same sentence fires for every
18
+ * `api.<verb>` call regardless of which variable blocks it). DEC-223 §1 asks
19
+ * for `attrs.blockedBy` to carry this and it does not exist in any shipped
20
+ * adapter yet — that request is open, addressed to whoever ships the next
21
+ * ledger-producing change. So this takes an **blockedBy string** directly.
22
+ * A caller with `attrs.blockedBy` available passes it straight through; a
23
+ * caller without it (today, every real batch) supplies whatever it already
24
+ * determined by reading source — which is what the TIER-A-2 measurement this
25
+ * module was built to close used.
26
+ *
27
+ * ## Formats read
28
+ *
29
+ * `.env*` and `wrangler.toml`'s `[vars]`/`[env.<name>.vars]` tables
30
+ * (`REQUEST-D-wrangler-vars-format.md`, DEC-245). Container manifests and
31
+ * image builds are already in `sources.ts`'s declaration scan but that scan
32
+ * discards the *value* half — it only proves a name is declared, never what
33
+ * it is bound to — and neither measured repository's real questions resolve
34
+ * through either format.
35
+ *
36
+ * The wrangler reading itself lives in `sources.ts` — `readWranglerVarsEntries`
37
+ * — the same file that owns every other format's structural read. This module
38
+ * only turns its output into `DeclaredEnvValue`s, the same way it already does
39
+ * for `readDotenvValues`; one reader per format, never a second one for the
40
+ * same file here.
41
+ *
42
+ * ## Never guessed, always refused
43
+ *
44
+ * `candidateEnvNames` returns `[]` — never a best guess — for any blockedBy
45
+ * shape it does not recognise. An blockedBy this module cannot parse stays
46
+ * exactly as open as it was before this module existed; principle 2 applies
47
+ * to a lookup the same way it applies to an edge.
48
+ */
49
+ import type { ResolutionLevel } from "@descryy/ir";
50
+ import type { ConfigSurface, SurfaceScan } from "./../validation/sources.ts";
51
+ /** A value this repository states for one variable, in one environment. */
52
+ export interface DeclaredEnvValue {
53
+ readonly name: string;
54
+ /** Derived from the surface's filename — see `environmentOf`'s header. */
55
+ readonly environment: string;
56
+ readonly value: string;
57
+ readonly file: string;
58
+ readonly line: number;
59
+ /** `.env.<environment>.local` — Vite's convention, wins over the non-local peer. */
60
+ readonly local: boolean;
61
+ /** `.env.example` and its siblings — documents that the name exists; the value is a placeholder. */
62
+ readonly example: boolean;
63
+ }
64
+ /** One candidate that satisfied an blockedBy, in one environment. */
65
+ export interface ResolvedValue {
66
+ readonly environment: string;
67
+ /** Which candidate name actually answered — relevant when `blockedBy` is a fallback chain. */
68
+ readonly name: string;
69
+ readonly value: string;
70
+ readonly file: string;
71
+ readonly line: number;
72
+ readonly local: boolean;
73
+ }
74
+ export interface LedgerAccessorResolution {
75
+ readonly blockedBy: string;
76
+ /** `[]` means the blockedBy's shape was not recognised — refused, not guessed. */
77
+ readonly candidates: readonly string[];
78
+ /** One per environment for which some candidate resolved. */
79
+ readonly resolved: readonly ResolvedValue[];
80
+ /**
81
+ * Every environment this repository's own `.env*` naming asserts exists —
82
+ * from filenames actually present, including example/placeholder files.
83
+ * Never invented: a repository with no `.env.production*` file anywhere
84
+ * does not get `"production"` added here on the assumption that one must
85
+ * exist. That is the difference between disclosing a real gap and
86
+ * guessing at one.
87
+ */
88
+ readonly knownEnvironments: readonly string[];
89
+ /** `knownEnvironments` minus `resolved`'s environments — the honest "still open" list. */
90
+ readonly unresolvedEnvironments: readonly string[];
91
+ /**
92
+ * DEC-244's precedent (corrected from an earlier mis-citation of DEC-111,
93
+ * which is unrelated): a direct read of a declarative document needs no
94
+ * resolution and is treated as R3, the same class of evidence
95
+ * `adapter-openapi`/`adapter-sql` use to reach R3 with no language adapter
96
+ * at all. Present only when at least one candidate was recognised — an
97
+ * unparseable blockedBy has no resolution to state because nothing was
98
+ * read.
99
+ */
100
+ readonly resolution: ResolutionLevel | null;
101
+ }
102
+ /**
103
+ * The candidate variable names an blockedBy could resolve to, in the order
104
+ * they would actually be tried at runtime. `[]` when any side of the
105
+ * blockedBy is neither a recognised accessor nor a literal fallback —
106
+ * refused as a whole rather than partially understood, per rule 2.
107
+ */
108
+ export declare function candidateEnvNames(blockedBy: string): readonly string[];
109
+ /**
110
+ * Every value this repository's `.env*` and `wrangler.toml` surfaces state,
111
+ * across every environment they name. Read once per repository;
112
+ * `resolveAgainstDeclared` runs many times against the result rather than
113
+ * rescanning per ledger row.
114
+ */
115
+ export declare function readDeclaredEnvValues(root: string, scan: SurfaceScan, readFile: (file: string) => string): readonly DeclaredEnvValue[];
116
+ /**
117
+ * Resolve one blocked blockedBy against every declared value this
118
+ * repository already has on disk. Never invents an environment, never picks
119
+ * a value from a different repository, never treats an example file's
120
+ * placeholder as an answer.
121
+ */
122
+ export declare function resolveAgainstDeclared(blockedBy: string, declared: readonly DeclaredEnvValue[]): LedgerAccessorResolution;
123
+ export type { ConfigSurface };
124
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/ledger/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG7E,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,oGAAoG;IACpG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,0FAA0F;IAC1F,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;IACnD;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;CAC7C;AA2DD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CActE;AAsCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,gBAAgB,EAAE,CA8CtI;AAgBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GACpC,wBAAwB,CAiD1B;AAED,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * TIER-A-2's value-lookup half — DEC-223, DEC-136.
3
+ *
4
+ * DEC-120/DEC-121 (`validation/{env,sources}.ts`, Lane D) already answer *"is
5
+ * this variable declared anywhere"*. A refusal-ledger row needs a different
6
+ * question answered: *"what value does this blocked blockedBy hold, per
7
+ * environment, from what the repository already has on disk."* Same
8
+ * declaration surfaces, a different consumer — this file only reads
9
+ * `sources.ts`'s exports, it does not change them. See `parallel-agents.md`
10
+ * §3's ledger-lookup row.
11
+ *
12
+ * ## What this deliberately does not take as input, and why
13
+ *
14
+ * The obvious signature would be `resolve(row: UnresolvedRef)`. `UnresolvedRef`
15
+ * does not carry the blocked blockedBy — `rawTarget` is the callee
16
+ * (`"fetch"`, `"api.get"`), and `reason` is human prose with no variable name
17
+ * in it (confirmed by reading real rows: the same sentence fires for every
18
+ * `api.<verb>` call regardless of which variable blocks it). DEC-223 §1 asks
19
+ * for `attrs.blockedBy` to carry this and it does not exist in any shipped
20
+ * adapter yet — that request is open, addressed to whoever ships the next
21
+ * ledger-producing change. So this takes an **blockedBy string** directly.
22
+ * A caller with `attrs.blockedBy` available passes it straight through; a
23
+ * caller without it (today, every real batch) supplies whatever it already
24
+ * determined by reading source — which is what the TIER-A-2 measurement this
25
+ * module was built to close used.
26
+ *
27
+ * ## Formats read
28
+ *
29
+ * `.env*` and `wrangler.toml`'s `[vars]`/`[env.<name>.vars]` tables
30
+ * (`REQUEST-D-wrangler-vars-format.md`, DEC-245). Container manifests and
31
+ * image builds are already in `sources.ts`'s declaration scan but that scan
32
+ * discards the *value* half — it only proves a name is declared, never what
33
+ * it is bound to — and neither measured repository's real questions resolve
34
+ * through either format.
35
+ *
36
+ * The wrangler reading itself lives in `sources.ts` — `readWranglerVarsEntries`
37
+ * — the same file that owns every other format's structural read. This module
38
+ * only turns its output into `DeclaredEnvValue`s, the same way it already does
39
+ * for `readDotenvValues`; one reader per format, never a second one for the
40
+ * same file here.
41
+ *
42
+ * ## Never guessed, always refused
43
+ *
44
+ * `candidateEnvNames` returns `[]` — never a best guess — for any blockedBy
45
+ * shape it does not recognise. An blockedBy this module cannot parse stays
46
+ * exactly as open as it was before this module existed; principle 2 applies
47
+ * to a lookup the same way it applies to an edge.
48
+ */
49
+ import { readWranglerVarsEntries } from "./../validation/sources.js";
50
+ /** Direct reads of `.env*` need no resolution — DEC-244's precedent, reused rather than restated. */
51
+ const DECLARATIVE_RESOLUTION = 3;
52
+ const ACCESSOR = [
53
+ /^import\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)$/,
54
+ /^process\.env\.([A-Za-z_][A-Za-z0-9_]*)$/,
55
+ /^process\.env\[["']([A-Za-z_][A-Za-z0-9_]*)["']\]$/,
56
+ /^env\.([A-Za-z_][A-Za-z0-9_]*)$/,
57
+ /^env\[["']([A-Za-z_][A-Za-z0-9_]*)["']\]$/,
58
+ ];
59
+ const LITERAL = /^(?:(["'`]).*\1|undefined|null|true|false|\d+(?:\.\d+)?)$/;
60
+ /**
61
+ * Split on top-level `||` / `??`, never inside a quoted literal.
62
+ *
63
+ * The blocked strings this reads are call-site fragments, not general JS — no
64
+ * nested ternaries, no parenthesised groups observed in either measured
65
+ * repository. Good enough for what real code does here; anything stranger
66
+ * fails a `parseSide` downstream and the whole blockedBy is refused rather
67
+ * than partially trusted.
68
+ */
69
+ function splitTopLevelFallback(blockedBy) {
70
+ const parts = [];
71
+ let depth = 0;
72
+ let quote = null;
73
+ let start = 0;
74
+ for (let i = 0; i < blockedBy.length; i += 1) {
75
+ const c = blockedBy[i];
76
+ if (quote !== null) {
77
+ if (c === quote && blockedBy[i - 1] !== "\\")
78
+ quote = null;
79
+ continue;
80
+ }
81
+ if (c === '"' || c === "'" || c === "`") {
82
+ quote = c;
83
+ continue;
84
+ }
85
+ if (c === "(" || c === "[")
86
+ depth += 1;
87
+ else if (c === ")" || c === "]")
88
+ depth -= 1;
89
+ else if (depth === 0 && (blockedBy.startsWith("||", i) || blockedBy.startsWith("??", i))) {
90
+ parts.push(blockedBy.slice(start, i));
91
+ i += 1;
92
+ start = i + 1;
93
+ }
94
+ }
95
+ parts.push(blockedBy.slice(start));
96
+ return parts.map((p) => p.trim()).filter((p) => p !== "");
97
+ }
98
+ function parseSide(side) {
99
+ for (const pattern of ACCESSOR) {
100
+ const match = pattern.exec(side);
101
+ if (match?.[1] !== undefined)
102
+ return match[1];
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * The candidate variable names an blockedBy could resolve to, in the order
108
+ * they would actually be tried at runtime. `[]` when any side of the
109
+ * blockedBy is neither a recognised accessor nor a literal fallback —
110
+ * refused as a whole rather than partially understood, per rule 2.
111
+ */
112
+ export function candidateEnvNames(blockedBy) {
113
+ const sides = splitTopLevelFallback(blockedBy);
114
+ if (sides.length === 0)
115
+ return [];
116
+ const names = [];
117
+ for (const side of sides) {
118
+ const name = parseSide(side);
119
+ if (name !== null) {
120
+ if (!names.includes(name))
121
+ names.push(name);
122
+ continue;
123
+ }
124
+ if (LITERAL.test(side))
125
+ continue; // a string/number/undefined fallback names nothing
126
+ return []; // an unrecognised shape anywhere refuses the whole blockedBy
127
+ }
128
+ return names;
129
+ }
130
+ const EXAMPLE_SUFFIX = /\.(example|sample|template|dist|defaults?)$/i;
131
+ const ENV_BASENAME = /^\.env(?:\.([A-Za-z0-9_-]+))?$/;
132
+ /** `.env`, `.env.local`, `.env.<name>`, `.env.<name>.local`, and each with an example suffix. */
133
+ function environmentOf(basename) {
134
+ const example = EXAMPLE_SUFFIX.test(basename) ? basename.replace(EXAMPLE_SUFFIX, "") : basename;
135
+ if (example === ".env" || example === ".env.local") {
136
+ return { environment: "default", local: example.endsWith(".local") };
137
+ }
138
+ const local = example.endsWith(".local");
139
+ const stem = local ? example.slice(0, -6) : example;
140
+ const match = ENV_BASENAME.exec(stem);
141
+ const name = match?.[1];
142
+ if (name === undefined)
143
+ return null;
144
+ return { environment: name, local };
145
+ }
146
+ /** `KEY=value` and `# KEY=value` (the commented-declaration case is skipped — no active value to read). */
147
+ function readDotenvValues(text) {
148
+ const out = [];
149
+ const lines = text.split(/\r?\n/);
150
+ for (let i = 0; i < lines.length; i += 1) {
151
+ const raw = lines[i] ?? "";
152
+ if (/^\s*#/.test(raw))
153
+ continue;
154
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(raw);
155
+ if (match === null)
156
+ continue;
157
+ const name = match[1];
158
+ let value = (match[2] ?? "").trim();
159
+ if (name === undefined)
160
+ continue;
161
+ const quoted = /^(["'])(.*)\1$/.exec(value);
162
+ if (quoted?.[2] !== undefined)
163
+ value = quoted[2];
164
+ out.push({ name, value, line: i + 1 });
165
+ }
166
+ return out;
167
+ }
168
+ /**
169
+ * Every value this repository's `.env*` and `wrangler.toml` surfaces state,
170
+ * across every environment they name. Read once per repository;
171
+ * `resolveAgainstDeclared` runs many times against the result rather than
172
+ * rescanning per ledger row.
173
+ */
174
+ export function readDeclaredEnvValues(root, scan, readFile) {
175
+ const out = [];
176
+ for (const surface of scan.surfaces) {
177
+ if (surface.format !== "dotenv" && surface.format !== "wranglerVars")
178
+ continue;
179
+ let text;
180
+ try {
181
+ text = readFile(`${root}/${surface.file}`);
182
+ }
183
+ catch {
184
+ continue; // unreadable — already surfaced by scanConfigSurfaces's own `unreadable` list
185
+ }
186
+ if (surface.format === "wranglerVars") {
187
+ // A commented-out `# KEY = "value"` line documents, it does not supply —
188
+ // the same rule `readDotenvValues` applies by simply never reading a
189
+ // commented line at all.
190
+ for (const entry of readWranglerVarsEntries(text)) {
191
+ if (entry.commented || entry.value === null)
192
+ continue;
193
+ out.push({
194
+ name: entry.name,
195
+ environment: entry.environment,
196
+ value: entry.value,
197
+ file: surface.file,
198
+ line: entry.line,
199
+ local: false,
200
+ example: false,
201
+ });
202
+ }
203
+ continue;
204
+ }
205
+ const basename = surface.file.split("/").pop() ?? surface.file;
206
+ const env = environmentOf(basename);
207
+ if (env === null)
208
+ continue;
209
+ for (const { name, value, line } of readDotenvValues(text)) {
210
+ out.push({
211
+ name,
212
+ environment: env.environment,
213
+ value,
214
+ file: surface.file,
215
+ line,
216
+ local: env.local,
217
+ example: surface.example,
218
+ });
219
+ }
220
+ }
221
+ return out;
222
+ }
223
+ /** `knownEnvironments` — every environment tag any `.env*` surface asserts, example files included. */
224
+ function knownEnvironmentsOf(declared) {
225
+ const set = new Set();
226
+ for (const d of declared)
227
+ set.add(d.environment);
228
+ return [...set].sort();
229
+ }
230
+ /** A `.local` value for the same (name, environment) outranks the non-local one, same as Vite resolves them. */
231
+ function pickBest(candidates) {
232
+ const local = candidates.find((c) => c.local && !c.example);
233
+ if (local !== undefined)
234
+ return local;
235
+ return candidates.find((c) => !c.example);
236
+ }
237
+ /**
238
+ * Resolve one blocked blockedBy against every declared value this
239
+ * repository already has on disk. Never invents an environment, never picks
240
+ * a value from a different repository, never treats an example file's
241
+ * placeholder as an answer.
242
+ */
243
+ export function resolveAgainstDeclared(blockedBy, declared) {
244
+ const candidates = candidateEnvNames(blockedBy);
245
+ const knownEnvironments = knownEnvironmentsOf(declared);
246
+ if (candidates.length === 0) {
247
+ return {
248
+ blockedBy,
249
+ candidates: [],
250
+ resolved: [],
251
+ knownEnvironments,
252
+ unresolvedEnvironments: knownEnvironments,
253
+ resolution: null,
254
+ };
255
+ }
256
+ const byEnvironment = new Map();
257
+ for (const d of declared) {
258
+ if (!candidates.includes(d.name))
259
+ continue;
260
+ const list = byEnvironment.get(d.environment) ?? [];
261
+ list.push(d);
262
+ byEnvironment.set(d.environment, list);
263
+ }
264
+ const resolved = [];
265
+ for (const environment of knownEnvironments) {
266
+ const options = byEnvironment.get(environment);
267
+ if (options === undefined)
268
+ continue;
269
+ // Earlier candidates win, same priority order the fallback chain itself uses.
270
+ for (const name of candidates) {
271
+ const forName = options.filter((o) => o.name === name);
272
+ const best = pickBest(forName);
273
+ if (best !== undefined) {
274
+ resolved.push({ environment, name, value: best.value, file: best.file, line: best.line, local: best.local });
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ const resolvedEnvironments = new Set(resolved.map((r) => r.environment));
280
+ const unresolvedEnvironments = knownEnvironments.filter((e) => !resolvedEnvironments.has(e));
281
+ return {
282
+ blockedBy,
283
+ candidates,
284
+ resolved,
285
+ knownEnvironments,
286
+ unresolvedEnvironments,
287
+ resolution: DECLARATIVE_RESOLUTION,
288
+ };
289
+ }
290
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/ledger/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * §14.5's missing half: producing the fingerprints `findSuperseded` compares.
3
+ *
4
+ * ## The two git semantics, and why they differ on purpose
5
+ *
6
+ * The question is *"is this change's work already on base?"*, and answering it
7
+ * needs **two different diffs**:
8
+ *
9
+ * - **What the change touched** comes from `base...head` — three dots, against
10
+ * the merge base. That is what a reviewer sees, and it does not include
11
+ * somebody else's commits that landed on base afterwards.
12
+ * - **What base holds now** comes from the **tip of base**, not the merge base.
13
+ *
14
+ * That asymmetry *is* the mechanism. If someone else landed the same work while
15
+ * this change sat open, the three-dot diff still shows the change touching the
16
+ * file — its merge base is old — while base's tip already matches. Comparing
17
+ * against the merge base instead would report every open change as unsuperseded
18
+ * forever, because a merge base by construction predates the change.
19
+ *
20
+ * ## Granularity is the honest limit, and it is not a resolution problem
21
+ *
22
+ * These are **whole-file** hashes — git's own blob ids, which is the strongest
23
+ * available statement that two versions are identical, and the weakest useful
24
+ * unit. A change that edited one file for two reasons, one of which landed, is
25
+ * reported as differing. That is the conservative direction and the correct one:
26
+ * the finding this feeds tells a human to close a pull request, so a missed
27
+ * supersession costs a review and a false one costs the work.
28
+ *
29
+ * Node-level fingerprints would need the same file re-parsed at two commits,
30
+ * which is adapter work rather than core's. When that exists it changes the
31
+ * basis string and nothing else — `findSuperseded` compares opaque strings by
32
+ * construction, so this is a producer swap, not a redesign.
33
+ *
34
+ * ## What is not language knowledge
35
+ *
36
+ * Nothing here reads code. It asks git for content hashes and compares them,
37
+ * which is why it can ship in core at all — rule 1 is satisfied by never
38
+ * looking inside the bytes.
39
+ */
40
+ import type { SupersededCandidate } from "./superseded.ts";
41
+ /** What `git ls-tree`/`diff` produced, and what a caller must be told about it. */
42
+ export interface CandidateReport {
43
+ readonly candidates: readonly SupersededCandidate[];
44
+ readonly notes: readonly string[];
45
+ /** The string `findSuperseded` should carry into its disclosure. */
46
+ readonly fingerprintBasis: string;
47
+ }
48
+ /**
49
+ * Fingerprint every file a change touched, as the change leaves it and as base
50
+ * holds it now.
51
+ *
52
+ * @param base the branch this change would merge into — compared at its **tip**
53
+ * @param head the tip of the change
54
+ */
55
+ export declare function supersededCandidates(cwd: string, base: string, head?: string): Promise<CandidateReport>;
56
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/multipr/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,oEAAoE;IACpE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAkED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,SAAS,GACZ,OAAO,CAAC,eAAe,CAAC,CAoE1B"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * §14.5's missing half: producing the fingerprints `findSuperseded` compares.
3
+ *
4
+ * ## The two git semantics, and why they differ on purpose
5
+ *
6
+ * The question is *"is this change's work already on base?"*, and answering it
7
+ * needs **two different diffs**:
8
+ *
9
+ * - **What the change touched** comes from `base...head` — three dots, against
10
+ * the merge base. That is what a reviewer sees, and it does not include
11
+ * somebody else's commits that landed on base afterwards.
12
+ * - **What base holds now** comes from the **tip of base**, not the merge base.
13
+ *
14
+ * That asymmetry *is* the mechanism. If someone else landed the same work while
15
+ * this change sat open, the three-dot diff still shows the change touching the
16
+ * file — its merge base is old — while base's tip already matches. Comparing
17
+ * against the merge base instead would report every open change as unsuperseded
18
+ * forever, because a merge base by construction predates the change.
19
+ *
20
+ * ## Granularity is the honest limit, and it is not a resolution problem
21
+ *
22
+ * These are **whole-file** hashes — git's own blob ids, which is the strongest
23
+ * available statement that two versions are identical, and the weakest useful
24
+ * unit. A change that edited one file for two reasons, one of which landed, is
25
+ * reported as differing. That is the conservative direction and the correct one:
26
+ * the finding this feeds tells a human to close a pull request, so a missed
27
+ * supersession costs a review and a false one costs the work.
28
+ *
29
+ * Node-level fingerprints would need the same file re-parsed at two commits,
30
+ * which is adapter work rather than core's. When that exists it changes the
31
+ * basis string and nothing else — `findSuperseded` compares opaque strings by
32
+ * construction, so this is a producer swap, not a redesign.
33
+ *
34
+ * ## What is not language knowledge
35
+ *
36
+ * Nothing here reads code. It asks git for content hashes and compares them,
37
+ * which is why it can ship in core at all — rule 1 is satisfied by never
38
+ * looking inside the bytes.
39
+ */
40
+ import { spawn } from "node:child_process";
41
+ import { cleanGitEnv } from "../sources/git/env.js";
42
+ const BASIS = "git blob identity at whole-file granularity — two versions are called identical when git " +
43
+ "gives them the same object id, and a difference means only that the file bytes differ";
44
+ /**
45
+ * Pathspecs are chunked rather than passed whole. A large change can exceed the
46
+ * argument limit, and the failure mode is an OS-level error whose message says
47
+ * nothing about paths — the kind of thing that gets diagnosed as a git problem.
48
+ */
49
+ const PATH_CHUNK = 200;
50
+ async function git(cwd, args) {
51
+ return new Promise((resolve, reject) => {
52
+ const child = spawn("git", args, { cwd, env: cleanGitEnv() });
53
+ let out = "";
54
+ let err = "";
55
+ child.stdout.on("data", (d) => (out += d.toString("utf8")));
56
+ child.stderr.on("data", (d) => (err += d.toString("utf8")));
57
+ child.on("error", reject);
58
+ child.on("close", (code) => {
59
+ if (code === 0)
60
+ resolve(out);
61
+ else
62
+ reject(new Error(`git ${args.slice(0, 2).join(" ")} failed: ${err.trim() || code}`));
63
+ });
64
+ });
65
+ }
66
+ /**
67
+ * Blob ids for `paths` at `ref`. A path absent from the tree is **absent from
68
+ * the map**, which the caller turns into `"absent"` — distinct from a git
69
+ * failure, which throws and becomes `"unknown"`.
70
+ *
71
+ * `-z` because a path may legally contain a newline. (It may not contain NUL,
72
+ * which is why NUL is the correct delimiter *on a pipe* even though it is the
73
+ * wrong thing to write into a file a human is meant to review.)
74
+ */
75
+ async function blobIds(cwd, ref, paths) {
76
+ const found = new Map();
77
+ for (let i = 0; i < paths.length; i += PATH_CHUNK) {
78
+ const chunk = paths.slice(i, i + PATH_CHUNK);
79
+ const out = await git(cwd, ["ls-tree", "-r", "-z", ref, "--", ...chunk]);
80
+ for (const record of out.split("\0")) {
81
+ if (record === "")
82
+ continue;
83
+ // "<mode> <type> <sha>\t<path>"
84
+ const tab = record.indexOf("\t");
85
+ if (tab === -1)
86
+ continue;
87
+ const meta = record.slice(0, tab).split(/\s+/);
88
+ const sha = meta[2];
89
+ const path = record.slice(tab + 1);
90
+ if (sha !== undefined && meta[1] === "blob")
91
+ found.set(path, sha);
92
+ }
93
+ }
94
+ return found;
95
+ }
96
+ function diffTypeOf(status) {
97
+ if (status.startsWith("A"))
98
+ return "added";
99
+ if (status.startsWith("D"))
100
+ return "deleted";
101
+ return "modified";
102
+ }
103
+ /**
104
+ * Fingerprint every file a change touched, as the change leaves it and as base
105
+ * holds it now.
106
+ *
107
+ * @param base the branch this change would merge into — compared at its **tip**
108
+ * @param head the tip of the change
109
+ */
110
+ export async function supersededCandidates(cwd, base, head = "HEAD") {
111
+ const notes = [];
112
+ // Three dots: what this change touched, against the merge base. Renames are
113
+ // followed so a moved file is one candidate rather than a delete and an add.
114
+ const statusOut = await git(cwd, ["diff", "--name-status", "-M", "-z", `${base}...${head}`]);
115
+ const touched = [];
116
+ const fields = statusOut.split("\0");
117
+ for (let i = 0; i < fields.length; i += 1) {
118
+ const status = fields[i];
119
+ if (status === undefined || status === "")
120
+ continue;
121
+ // A rename record is `R<score>\0<old>\0<new>` — three fields, and the new
122
+ // path is the one that exists to be compared.
123
+ if (status.startsWith("R") || status.startsWith("C")) {
124
+ const to = fields[i + 2];
125
+ if (to !== undefined && to !== "")
126
+ touched.push({ path: to, diffType: "modified" });
127
+ i += 2;
128
+ continue;
129
+ }
130
+ const path = fields[i + 1];
131
+ if (path !== undefined && path !== "")
132
+ touched.push({ path, diffType: diffTypeOf(status) });
133
+ i += 1;
134
+ }
135
+ if (touched.length === 0) {
136
+ notes.push(`No file differs between ${base} and ${head}, so there was nothing to fingerprint. An ` +
137
+ "empty diff is a real answer and is not the same as a change whose work is already on base.");
138
+ return { candidates: [], notes, fingerprintBasis: BASIS };
139
+ }
140
+ const paths = touched.map((t) => t.path);
141
+ // The tip of base, deliberately — see the header. Resolved once so a branch
142
+ // that moves mid-run cannot make two halves of one answer disagree.
143
+ const baseSha = (await git(cwd, ["rev-parse", base])).trim();
144
+ const [afterBlobs, baseBlobs] = await Promise.all([
145
+ blobIds(cwd, head, paths),
146
+ blobIds(cwd, baseSha, paths),
147
+ ]);
148
+ const candidates = touched.map((t) => ({
149
+ nodeId: t.path,
150
+ file: t.path,
151
+ diffType: t.diffType,
152
+ after: afterBlobs.get(t.path) ?? "absent",
153
+ base: baseBlobs.get(t.path) ?? "absent",
154
+ // R0: nothing was parsed. The comparison itself is exact — git object ids
155
+ // are equal only for identical content — but the *claim* rests on
156
+ // whole-file granularity being the right unit for "this work is already
157
+ // there", which is a judgement no resolution level supports. Rule 2's
158
+ // direction: understate.
159
+ resolution: 0,
160
+ }));
161
+ notes.push(`${candidates.length} file(s) fingerprinted at ${head} and at ${base} (${baseSha.slice(0, 12)}). ` +
162
+ "Base is compared at its tip rather than at the merge base: if this work landed by another " +
163
+ "route while the change was open, the merge base still predates it and only the tip shows it.");
164
+ notes.push("Fingerprints are whole-file. A file edited for two reasons, one of which landed, reports as " +
165
+ "differing — the conservative direction, and the right one for a check whose finding tells " +
166
+ "someone to discard work.");
167
+ return { candidates, notes, fingerprintBasis: BASIS };
168
+ }
169
+ //# sourceMappingURL=fingerprint.js.map