@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,279 @@
1
+ /**
2
+ * The batch writer — DEC-015.
3
+ *
4
+ * "The writer deletes everything previously written by `producedBy` for
5
+ * `sourceFiles`, then inserts, in one transaction." That sentence hides three
6
+ * decisions the decision entry does not make, and getting any of them wrong
7
+ * loses data silently. They are made here and each is tested.
8
+ *
9
+ * ## 1. Invalidation matches on adapter *id* and *repo*, not `producedBy` alone
10
+ *
11
+ * `producedBy` is `id@version`. Matching the full string would mean an adapter
12
+ * upgrade never invalidates anything the old version wrote — the two producers
13
+ * would simply coexist, and the graph would hold both readings of every file
14
+ * forever. §24.1 is explicit: "an adapter version bump invalidates every node
15
+ * and edge that adapter produced". So overlap is computed on the id, and the
16
+ * version is what makes the *stale* rows recognisable.
17
+ *
18
+ * `repo` is a second, equally load-bearing part of the same match, added after
19
+ * a real defect: a store holding more than one repository (`descry-runtime`'s
20
+ * graph correlator merges a frontend and a backend into one store to trace
21
+ * across them, per DEC-228) can have two repos using the same adapter with an
22
+ * overlapping relative file path — `src/index.ts`, `package.json`, anything
23
+ * common. Without `repo` in this match, writing the second repo's batch reads
24
+ * as "adapter X re-ran over a file it already owned" and evicts the first
25
+ * repo's run outright — silent cross-repository data loss, not a wrong
26
+ * answer. `invalidateAdapter` below is intentionally *not* repo-scoped — its
27
+ * own contract is "every repo," a different, explicitly global operation.
28
+ *
29
+ * ## 2. Prior runs are matched by file-set *overlap*, not equality
30
+ *
31
+ * A batch is the unit of invalidation, but the batch that previously owned a
32
+ * file may have covered a different set of files. Deleting only exact-matching
33
+ * runs leaves two rows for the same symbol; deleting only the intersection is
34
+ * impossible, because an edge has no file. So every prior run of the same
35
+ * adapter that touches *any* of these files is replaced whole — and the files
36
+ * it covered that this batch does not re-supply are reported as `evictedFiles`.
37
+ * Reporting them is the honest-degradation rule (principle 7): the caller is
38
+ * told what fell out, rather than discovering it as a missing edge next week.
39
+ *
40
+ * ## 3. A run also cleans up after its own previous write
41
+ *
42
+ * The run id is content-derived from producer, repo, commit and file set — not
43
+ * from the nodes. So an adapter re-run at the same commit over the same files
44
+ * lands on the *same* run id, and "delete the prior runs" excludes it by
45
+ * definition. Without a self-clean pass, rows that run no longer produces stay
46
+ * forever: the upsert refreshes what is still there and nothing removes what is
47
+ * not. Every write therefore ends by deleting its own run's rows that this batch
48
+ * did not supply.
49
+ *
50
+ * ## 4. Re-supplied nodes are updated, not deleted and recreated
51
+ *
52
+ * A node id is stable across file moves by construction (DEC-004), so re-parsing
53
+ * a file normally yields the ids that were already there. Deleting a prior run's
54
+ * nodes wholesale would cascade away every edge *other* runs had pointing into
55
+ * them, and those runs will not be re-run. Instead: upsert the ids this batch
56
+ * supplies, then delete the prior run — whose remaining nodes are exactly those
57
+ * it no longer produces. Edges into a node that genuinely disappeared do cascade
58
+ * away, and are reported as `orphanedEdges`, because keeping an edge to a node
59
+ * that no longer exists is the wrong-edge failure the precision rule forbids.
60
+ */
61
+ import { createHash } from "node:crypto";
62
+ import { canonicalJson, edgeId } from "@descryy/ir";
63
+ import { EDGE_MERGE_CONDITION, NODE_MERGE_CONDITION } from "../graph/merge.js";
64
+ /** Split `adapterId@version`. The Normaliser has already guaranteed the shape. */
65
+ function splitProducer(producedBy) {
66
+ const at = producedBy.lastIndexOf("@");
67
+ return { id: producedBy.slice(0, at), version: producedBy.slice(at + 1) };
68
+ }
69
+ function hash128(input) {
70
+ return createHash("sha256").update(input, "utf8").digest("hex").slice(0, 32);
71
+ }
72
+ /**
73
+ * A run id derived from the batch's content, never a counter.
74
+ *
75
+ * Re-ingesting an identical batch must land on the same row, or "idempotent"
76
+ * cannot even be stated as a property. `sourceFiles` is sorted first so that an
77
+ * adapter listing the same files in a different order does not create a second
78
+ * run for identical work.
79
+ */
80
+ export function runIdFor(batch) {
81
+ return `r1:${hash128(canonicalJson([batch.producedBy, batch.repo, batch.commitSha, [...batch.sourceFiles].sort()]))}`;
82
+ }
83
+ function unresolvedId(ref) {
84
+ return `u1:${hash128(canonicalJson([ref.fromNodeId, ref.edgeType, ref.rawTarget, ref.file, ref.line, ref.producedBy]))}`;
85
+ }
86
+ /**
87
+ * **Delegates to `@descryy/ir`'s `edgeId()` — it must not re-derive this.**
88
+ *
89
+ * It used to, as a hand-rolled `` `e1:${hash128(...)}` ``, and that was a
90
+ * latent identity split: DEC-054 moved the prefixes to `n2:`/`e2:` and this
91
+ * copy was never moved with them, so `edgeId()` and the stored id agreed on
92
+ * every byte of the hash and disagreed on the prefix. Nothing caught it
93
+ * because `edgeId()` had zero callers — the first caller to look an edge up
94
+ * by its public id would have silently matched nothing, which is DEC-055's
95
+ * measured failure (16 joins to 0, no error anywhere) reproduced inside the
96
+ * engine rather than across two adapters.
97
+ *
98
+ * One identity, one function. See DEC-NEXT-edge-id-prefix-split.
99
+ */
100
+ function edgeRowId(edge) {
101
+ return edgeId(edge.from, edge.to, edge.type);
102
+ }
103
+ /**
104
+ * Write one normalised batch. **Input must already have passed the Normaliser** —
105
+ * this function does no validation, on purpose. Two validators drift; one
106
+ * validator at the boundary is a boundary.
107
+ */
108
+ export function writeBatch(driver, batch, options = {}) {
109
+ const now = options.now ?? Date.now;
110
+ const runId = runIdFor(batch);
111
+ const { id: adapterId, version } = splitProducer(batch.producedBy);
112
+ const incomingFiles = new Set(batch.sourceFiles);
113
+ return driver.transaction(() => {
114
+ // --- find the prior runs this batch supersedes --------------------------
115
+ const priorRuns = driver
116
+ .prepare("SELECT id, source_files FROM adapter_runs WHERE adapter_id = ? AND repo = ? AND id != ?")
117
+ .all(adapterId, batch.repo, runId)
118
+ .filter((row) => {
119
+ const files = JSON.parse(row.source_files);
120
+ return files.some((f) => incomingFiles.has(f));
121
+ });
122
+ const evicted = new Set();
123
+ for (const run of priorRuns) {
124
+ for (const file of JSON.parse(run.source_files)) {
125
+ if (!incomingFiles.has(file))
126
+ evicted.add(file);
127
+ }
128
+ }
129
+ const ownRunIds = [runId, ...priorRuns.map((r) => r.id)];
130
+ const foreignEdgesBefore = countForeignEdges(driver, ownRunIds);
131
+ // A scratch list of what this batch supplies, so the self-clean below is a
132
+ // set difference rather than a `NOT IN (?, ?, ...)` with one parameter per
133
+ // row — which caps out at SQLITE_MAX_VARIABLE_NUMBER on a large batch.
134
+ driver.exec(`CREATE TEMP TABLE IF NOT EXISTS supplied (kind TEXT NOT NULL, id TEXT NOT NULL, PRIMARY KEY (kind, id))`);
135
+ driver.exec("DELETE FROM temp.supplied");
136
+ const remember = driver.prepare("INSERT OR IGNORE INTO temp.supplied (kind, id) VALUES (?, ?)");
137
+ // Rows owned by a run this write replaces are always overwritten, never
138
+ // merged. DEC-012 arbitrates between two *different* producers claiming the
139
+ // same thing; the same adapter re-running over the same files is
140
+ // invalidation, and the fresh reading wins even when it reached a lower
141
+ // resolution — stale R3 facts about code that has since changed are worse
142
+ // than accurate R1 ones. Without this, a degraded re-run would fail the
143
+ // merge test, leave the row owned by the prior run, and then lose it to that
144
+ // run's cascade a few statements later.
145
+ for (const run of priorRuns)
146
+ remember.run("run", run.id);
147
+ // --- the run row, before anything references it -------------------------
148
+ driver
149
+ .prepare(`INSERT INTO adapter_runs
150
+ (id, repo, workspace, commit_sha, adapter_id, adapter_version, produced_by,
151
+ reached_resolution, source_files, file_count, written_at)
152
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
153
+ ON CONFLICT(id) DO UPDATE SET
154
+ commit_sha = excluded.commit_sha,
155
+ adapter_version = excluded.adapter_version,
156
+ produced_by = excluded.produced_by,
157
+ reached_resolution = excluded.reached_resolution,
158
+ written_at = excluded.written_at`)
159
+ .run(runId, batch.repo, batch.workspace ?? null, batch.commitSha, adapterId, version, batch.producedBy, batch.reachedResolution, JSON.stringify([...batch.sourceFiles].sort()), batch.sourceFiles.length, now());
160
+ // ON CONFLICT DO UPDATE, never INSERT OR REPLACE. `REPLACE` is a DELETE
161
+ // followed by an INSERT, so with foreign keys on it would cascade away this
162
+ // run's own nodes and edges before re-inserting them — and because the run
163
+ // id is content-derived, re-ingesting an identical batch hits that path
164
+ // every single time.
165
+ // --- nodes: upsert first, so ids that survive keep their inbound edges ---
166
+ const insertNode = driver.prepare(`INSERT INTO nodes
167
+ (id, type, name, file_path, start_line, end_line, language, produced_by, resolution, attrs, run_id)
168
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
169
+ ON CONFLICT(id) DO UPDATE SET
170
+ type = excluded.type, name = excluded.name, file_path = excluded.file_path,
171
+ start_line = excluded.start_line, end_line = excluded.end_line,
172
+ language = excluded.language, produced_by = excluded.produced_by,
173
+ resolution = excluded.resolution, attrs = excluded.attrs, run_id = excluded.run_id
174
+ WHERE nodes.run_id = excluded.run_id
175
+ OR nodes.run_id IN (SELECT id FROM temp.supplied WHERE kind = 'run')
176
+ OR (${NODE_MERGE_CONDITION})`);
177
+ for (const node of batch.nodes) {
178
+ insertNode.run(node.id, node.type, node.name, node.file, node.range?.startLine ?? null, node.range?.endLine ?? null, node.language, node.producedBy, node.resolution, canonicalJson(node.attrs), runId);
179
+ remember.run("node", node.id);
180
+ }
181
+ // --- retire the prior runs ----------------------------------------------
182
+ // Everything still pointing at them is, by definition, what this batch no
183
+ // longer produces. The cascade removes it; the counts above and below say
184
+ // how much.
185
+ if (priorRuns.length > 0) {
186
+ const del = driver.prepare("DELETE FROM adapter_runs WHERE id = ?");
187
+ for (const run of priorRuns)
188
+ del.run(run.id);
189
+ }
190
+ // ...and after this run's own previous write. See note 3 above: an adapter
191
+ // re-run at the same commit reuses the run id, so this is the only thing
192
+ // that removes what it stopped producing.
193
+ driver
194
+ .prepare(`DELETE FROM nodes WHERE run_id = ?
195
+ AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'node')`)
196
+ .run(runId);
197
+ // --- edges and unresolved references -------------------------------------
198
+ const insertEdge = driver.prepare(`INSERT INTO edges
199
+ (id, source, target, type, resolution, confidence, produced_by, observed_by_run, attrs, run_id)
200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
201
+ ON CONFLICT(id) DO UPDATE SET
202
+ resolution = excluded.resolution, confidence = excluded.confidence,
203
+ produced_by = excluded.produced_by, observed_by_run = excluded.observed_by_run,
204
+ attrs = excluded.attrs, run_id = excluded.run_id
205
+ WHERE edges.run_id = excluded.run_id
206
+ OR edges.run_id IN (SELECT id FROM temp.supplied WHERE kind = 'run')
207
+ OR (${EDGE_MERGE_CONDITION})`);
208
+ for (const edge of batch.edges) {
209
+ const id = edgeRowId(edge);
210
+ remember.run("edge", id);
211
+ insertEdge.run(id, edge.from, edge.to, edge.type, edge.resolution, edge.confidence, edge.producedBy, edge.observedByRun ?? null, edge.attrs === undefined ? null : canonicalJson(edge.attrs), runId);
212
+ }
213
+ const insertRef = driver.prepare(`INSERT INTO unresolved_refs
214
+ (id, from_node_id, edge_type, raw_target, file_path, line, produced_by, reason, attrs, run_id)
215
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
216
+ ON CONFLICT(id) DO UPDATE SET reason = excluded.reason, attrs = excluded.attrs, run_id = excluded.run_id`);
217
+ driver
218
+ .prepare(`DELETE FROM edges WHERE run_id = ?
219
+ AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'edge')`)
220
+ .run(runId);
221
+ for (const ref of batch.unresolved) {
222
+ const id = unresolvedId(ref);
223
+ remember.run("ref", id);
224
+ insertRef.run(id, ref.fromNodeId, ref.edgeType, ref.rawTarget, ref.file, ref.line, ref.producedBy, ref.reason, ref.attrs === undefined ? null : canonicalJson(ref.attrs), runId);
225
+ }
226
+ driver
227
+ .prepare(`DELETE FROM unresolved_refs WHERE run_id = ?
228
+ AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'ref')`)
229
+ .run(runId);
230
+ // Edges belonging to neither this run nor the runs it replaced can only have
231
+ // vanished by cascade — an endpoint node ceased to exist. Measured directly
232
+ // rather than inferred from a before/after total, which dedupe would skew.
233
+ const orphaned = foreignEdgesBefore - countForeignEdges(driver, ownRunIds);
234
+ return {
235
+ runId,
236
+ nodesWritten: batch.nodes.length,
237
+ edgesWritten: batch.edges.length,
238
+ unresolvedWritten: batch.unresolved.length,
239
+ runsReplaced: priorRuns.length,
240
+ evictedFiles: [...evicted].sort(),
241
+ orphanedEdges: orphaned,
242
+ };
243
+ });
244
+ }
245
+ /** Edges owned by some run other than the ones this write is touching. */
246
+ function countForeignEdges(driver, ownRunIds) {
247
+ const placeholders = ownRunIds.map(() => "?").join(", ");
248
+ return (driver
249
+ .prepare(`SELECT COUNT(*) AS n FROM edges WHERE run_id NOT IN (${placeholders})`)
250
+ .get(...ownRunIds)?.n ?? 0);
251
+ }
252
+ /**
253
+ * Remove everything an adapter ever produced, at every version, for every repo.
254
+ *
255
+ * §24.1's invalidation rule, as its own operation rather than a side effect of
256
+ * writing. It is deliberately separate from `writeBatch`: an upgraded adapter
257
+ * usually re-runs over a subset of files first, and folding this into the write
258
+ * path would make a partial re-run delete data it was never going to replace.
259
+ */
260
+ export function invalidateAdapter(driver, adapterId) {
261
+ return driver.transaction(() => {
262
+ const result = driver.prepare("DELETE FROM adapter_runs WHERE adapter_id = ?").run(adapterId);
263
+ return { runsRemoved: result.changes };
264
+ });
265
+ }
266
+ /** §11.14 rebuild acceleration. Layer 1 change detection reads this. */
267
+ export function putFileHash(driver, filePath, hash, parsedAt) {
268
+ driver
269
+ .prepare(`INSERT INTO file_hashes (file_path, hash, last_parsed_at) VALUES (?, ?, ?)
270
+ ON CONFLICT(file_path) DO UPDATE SET hash = excluded.hash, last_parsed_at = excluded.last_parsed_at`)
271
+ .run(filePath, hash, parsedAt);
272
+ }
273
+ /** Nodes touching these files, unchanged. */
274
+ export function getFileHash(driver, filePath) {
275
+ return driver
276
+ .prepare("SELECT hash FROM file_hashes WHERE file_path = ?")
277
+ .get(filePath)?.hash;
278
+ }
279
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/store/writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,375 @@
1
+ /**
2
+ * The capability-tier generator. DEC-075 §6, in code.
3
+ *
4
+ * > *"A tier is generated from `capabilities()`, never written in prose, and may
5
+ * > be published for a language only when the corpus passes at the resolution
6
+ * > the tier claims, a ≥200-edge precision draw passes on at least two
7
+ * > repositories the adapter's author did not choose, and `capabilities()`
8
+ * > declares exactly the node and edge types the adapter emitted."*
9
+ *
10
+ * ## The design point: this thing exists to say no
11
+ *
12
+ * A generator that awards a tier to everything it is shown is a rubber stamp
13
+ * with a build step. The output that matters is **the row that failed**, which
14
+ * is why `unmet` is a list of requirements with their statements rather than a
15
+ * boolean, and why `certify` never returns a bare tier without also returning
16
+ * what the next tier up is missing.
17
+ *
18
+ * Two rules follow from that, and both are the opposite of what is convenient:
19
+ *
20
+ * 1. **Absent evidence is not a pass.** No precision draw recorded means the
21
+ * attested requirement is unmet, exactly as if a recorded draw had failed.
22
+ * The alternative — treating silence as satisfaction — is how every unearned
23
+ * claim in this repository would have been generated.
24
+ * 2. **An attested requirement is labelled attested, in the output, always.**
25
+ * A machine can check "≥200 edges" and "two repositories". It cannot check
26
+ * *"the author did not choose them"*, because that is a fact about a person's
27
+ * intent. Marking it as declared-by-a-human is what stops this function
28
+ * laundering a judgement into a computed result. `certify` therefore reports
29
+ * `attestedBy` alongside the tier, and a consumer that prints the tier
30
+ * without it has dropped the only part that says who is responsible.
31
+ *
32
+ * ## What it does not do
33
+ *
34
+ * It does not run the conformance harness or the precision benches. It is given
35
+ * their results. That keeps it pure and testable, and it keeps the awkward fact
36
+ * visible: the precision evidence is **recorded by hand today**, because the
37
+ * benches are throwaway scripts that print to a terminal. `GateEvidence` is
38
+ * where that hand-recording is written down and dated, rather than living in a
39
+ * decision document that nothing reads.
40
+ */
41
+ import type { CapabilityMatrix, EdgeType, NodeType, ResolutionLevel } from "@descryy/ir";
42
+ import type { RefusalCensus } from "../query/unresolved.ts";
43
+ import { type Requirement, type Tier } from "./ladder.ts";
44
+ /**
45
+ * One adjudicated precision draw, as DEC-075 §6.2 requires it.
46
+ *
47
+ * `authorChose` is the field that makes this honest. It defaults to nothing —
48
+ * a recorder who has not thought about it produces an incomplete record rather
49
+ * than an accidental pass, the same reason `reliabilityCap`'s `nameLevel` has
50
+ * no default.
51
+ */
52
+ export interface PrecisionDraw {
53
+ readonly repository: string;
54
+ readonly edgeType: EdgeType | "mixed";
55
+ readonly drawn: number;
56
+ readonly correct: number;
57
+ /**
58
+ * How many of `drawn` the adjudicator actually rendered a verdict on —
59
+ * `undefined` when every adjudicator this project has ever recorded a draw
60
+ * with decides every row it draws, so `decided` and `drawn` have always
61
+ * been the same number and no draw needed to say so until now.
62
+ *
63
+ * **Kotlin is the first adapter where they diverge, and the divergence
64
+ * exposed a real instrument defect.** `correct / drawn` silently punishes
65
+ * an adjudicator for honestly declining to decide a row exactly as if it
66
+ * had decided that row wrong — okhttp drew 200, decided 192, and every one
67
+ * of the 192 it *did* decide was correct (192/192 = 100%, its own
68
+ * pre-registered gate: GATE PASSED); `correct / drawn` reads that as
69
+ * 192/200 = 96%, below the 98% floor, for a reason that has nothing to do
70
+ * with the adapter's precision. Precision is measured over `decided` rows
71
+ * only (see `checkPrecisionDraws`); `decided / drawn` — coverage — is a
72
+ * separate, always-disclosed axis (`bench/tier-matrix.mjs`'s
73
+ * `describeDrawCoverage`), never folded into the precision figure: a draw
74
+ * with high coverage and lower precision and one with low coverage and
75
+ * clean precision are different facts, and burying the second inside a
76
+ * clean-looking percentage is the failure this field exists to prevent.
77
+ */
78
+ readonly decided?: number;
79
+ /**
80
+ * How many candidates of this edge type the repository contained — the
81
+ * population the draw was taken **from**, not the sample size.
82
+ *
83
+ * ## Zero out of zero is undefined here too
84
+ *
85
+ * Without it, `0 edges drawn, below 200` and `47 edges drawn, below 200` print
86
+ * identically, and they are different facts with different remedies:
87
+ *
88
+ * | | statement about | what to do |
89
+ * | --- | --- | --- |
90
+ * | population 0 | **the corpus** — this reference set contains nothing to sample | find other repositories, or accept the bar is unreachable here |
91
+ * | population > 0, drawn short | **the adapter** — measured and short | measure more |
92
+ *
93
+ * Python is the live case: `USES_API` is emitted and measures **0 edges** on
94
+ * both reference repositories, and that zero is *correct* — a backend service
95
+ * in this reference set is a provider rather than a consumer, so no 200-edge
96
+ * draw is reachable from it at all. Reporting that as "below 200" tells a
97
+ * reader to measure harder, when the honest answer is that **this reference
98
+ * set cannot answer the question**.
99
+ *
100
+ * `undefined` means the population was not recorded, which is neither zero nor
101
+ * non-zero and is reported as its own case — the same reason
102
+ * `capabilitiesHonest` has an `"unknown"`.
103
+ */
104
+ readonly population?: number;
105
+ /**
106
+ * The refusal census for this draw's edge type, from `classifyRefusals`.
107
+ *
108
+ * ## Why the census rather than a recorded "drawable" count
109
+ *
110
+ * The number that decides reachability is **population minus `outOfScope`**:
111
+ * a call read correctly that targets something this workspace does not serve
112
+ * can never become an edge here, however good the adapter gets.
113
+ *
114
+ * | | population | outOfScope | drawable | reads as |
115
+ * | --- | --- | --- | --- | --- |
116
+ * | `saleor` | 0 | 0 | **0** | nothing to sample |
117
+ * | `dispatch` | 4 | 4 | **0** | four third-party calls, refused forever |
118
+ * | `discourse` | 20 | 0 | **20** | a capability gap the adapter owns |
119
+ *
120
+ * Carrying the census rather than a recorded count keeps this **derived**, so
121
+ * a measured zero still attests to itself and needs no attribution — unlike a
122
+ * `StructuralLimit`. And it cannot drift from the ledger, **because it is the
123
+ * ledger**: a separately recorded "drawable" number is the same fact in two
124
+ * places, and this project has measured what that costs.
125
+ *
126
+ * ## The abuse route, which is why `scopeKnown` is load-bearing
127
+ *
128
+ * With no internal hosts supplied, `classifyRefusals` classifies **every**
129
+ * absolute target as `outOfScope` and says so — it "understates the deficit".
130
+ * Subtracting blindly there would let an adapter that declares no hosts write
131
+ * off every external-looking call as unreachable and **improve its tier by
132
+ * supplying less information**. So `scopeKnown === false` makes the
133
+ * subtraction **unavailable**, never zero.
134
+ *
135
+ * Absent entirely, `drawable` falls back to the full population — which blames
136
+ * the **adapter** rather than the corpus. That is the safe direction: the
137
+ * failure being guarded is a tier improved by withholding evidence.
138
+ */
139
+ readonly refusals?: RefusalCensus;
140
+ /** The negative control over the same adjudicator. Must be ~0. */
141
+ readonly controlDrawn: number;
142
+ readonly controlCorrect: number;
143
+ /**
144
+ * Did the adapter's author pick this repository? DEC-075 §6.2 requires two
145
+ * where the answer is **no**. Declared, never inferred — see the header.
146
+ */
147
+ readonly authorChose: boolean;
148
+ /** Who recorded this, so an attested claim has a name against it. */
149
+ readonly recordedBy: string;
150
+ readonly recordedOn: string;
151
+ /**
152
+ * The mixed draw's own composition, by edge type — `undefined` for every
153
+ * draw recorded before this field existed, and that absence is itself
154
+ * meaningful (see `checkCapability`'s use of it: no data means the
155
+ * type-floor check does not run for that draw, not that it fails).
156
+ *
157
+ * The gap this closes: DEC-221 made the precision draw whole-graph and
158
+ * NOT edge-type-scoped (a `USES_API`-only draw structurally cannot carry
159
+ * a negative control, DEC-164 §2). That is still correct for what a
160
+ * *draw* needs to qualify. But a language's tier can rest on a capability
161
+ * (`A.callers` on `USES_API`, `A.routes` on `SERVES_API`) that the draw
162
+ * happened to sample almost none of — Kotlin's ktor draw cleared 98%
163
+ * precision overall while carrying exactly one decided `USES_API` row,
164
+ * which is evidence of nothing about that specific capability. This field
165
+ * is what lets `checkCapability` tell "the capability was validated" from
166
+ * "the capability was declared and the aggregate draw happened to pass".
167
+ */
168
+ readonly perEdgeType?: Readonly<Record<string, {
169
+ readonly decided: number;
170
+ readonly correct: number;
171
+ }>>;
172
+ }
173
+ export interface ConformanceEvidence {
174
+ readonly resolution: ResolutionLevel;
175
+ readonly passed: boolean;
176
+ /**
177
+ * DEC-070's **adapter** verdict — no type emitted that `capabilities()` did
178
+ * not declare.
179
+ *
180
+ * Deliberately not "the manifest check produced no findings". That check
181
+ * reports two different things and only one of them is the adapter's fault:
182
+ * `adapterFailed` (emitted-but-undeclared, a real overclaim) and `corpusThin`
183
+ * (declared-but-unexercised, a fact about the corpus). Reading them as one
184
+ * failed every adapter in the repository at Tier C on its first run here,
185
+ * because a low-resolution sweep leaves plenty declared and unexercised.
186
+ */
187
+ readonly manifestClean: boolean;
188
+ }
189
+ export interface GateEvidence {
190
+ readonly adapter: string;
191
+ readonly language: string;
192
+ readonly capabilities: CapabilityMatrix;
193
+ /** What the adapter actually emitted, if a run is available to compare. */
194
+ readonly emittedNodeTypes?: readonly NodeType[];
195
+ readonly emittedEdgeTypes?: readonly EdgeType[];
196
+ readonly conformance: readonly ConformanceEvidence[];
197
+ readonly precisionDraws: readonly PrecisionDraw[];
198
+ /**
199
+ * Types the adapter declares that **no corpus pattern exercised**.
200
+ *
201
+ * `checkManifest` already computes this and calls it `unpublishable`, with
202
+ * the comment *"not publishable as a tier claim"* — written before any tier
203
+ * generator existed. It is exactly right: a declared type nothing exercised
204
+ * is an untested claim, and a tier resting on it rests on a promise. Treated
205
+ * here as not satisfying a capability requirement at all, with its own reason
206
+ * so it is distinguishable from a type that was never declared.
207
+ */
208
+ readonly unpublishableTypes?: readonly string[];
209
+ /**
210
+ * Capabilities that **cannot exist in this language**, whatever anyone builds.
211
+ *
212
+ * DEC-123's case: `REQUIRED_NOT_DECLARED` fires only when an environment
213
+ * accessor *fails* on absence, and JavaScript has no such form — `process.env.X`
214
+ * yields undefined. No adapter effort makes that finding reachable in
215
+ * TypeScript. Without this, the generator scores it as **absent evidence**,
216
+ * which is the verdict for *"nobody has built it yet"*, and the two collapse
217
+ * into one output.
218
+ *
219
+ * That collapse is the one distinction §20.2 exists to hold: *"we found
220
+ * nothing"* and *"we cannot check this"* are different sentences. At the tier
221
+ * layer the difference is between a gap someone should close and a boundary
222
+ * nobody can.
223
+ *
224
+ * **A limit does not block the tier, and it does not vanish either.** It is
225
+ * carried onto the certificate and printed, so the tier is published *with*
226
+ * its qualifier. Awarding the tier silently would claim the capability;
227
+ * withholding it forever would punish a language for its own semantics.
228
+ *
229
+ * `id` matches a requirement id (`A.callers`) or names a check
230
+ * (`REQUIRED_NOT_DECLARED`). `declaredBy` is required for the same reason
231
+ * `PrecisionDraw.recordedBy` is: **this is a human judgement, and an
232
+ * unattributable one is a way to dodge a requirement that is merely unbuilt.**
233
+ * A limit with no reason or no declarer is ignored, and the requirement is
234
+ * checked normally.
235
+ */
236
+ readonly structuralLimits?: readonly StructuralLimit[];
237
+ /**
238
+ * Capabilities claimed **below** what the evidence supports, pending an open
239
+ * policy. See `PolicyCap` — this neither waives a requirement nor blocks a
240
+ * tier; it stops a deliberate cap being published as an absence.
241
+ */
242
+ readonly policyCaps?: readonly PolicyCap[];
243
+ }
244
+ /** A capability this language cannot have. Declared, never inferred. */
245
+ export interface StructuralLimit {
246
+ /** A requirement id, or the name of a check. */
247
+ readonly id: string;
248
+ /** Why it is unreachable *in this language*, in terms of the language. */
249
+ readonly reason: string;
250
+ /** Who declared it. An anonymous structural limit is not admissible. */
251
+ readonly declaredBy: string;
252
+ }
253
+ /**
254
+ * A capability we **could** claim higher and are deliberately claiming lower,
255
+ * pending a policy that has not been written.
256
+ *
257
+ * ## The third state, and why two were not enough
258
+ *
259
+ * The certificate could say two things about a capability it does not claim:
260
+ *
261
+ * | | meaning |
262
+ * | --- | --- |
263
+ * | **absent** | we have not measured it |
264
+ * | **unreachable** (`StructuralLimit`) | this language structurally cannot produce it |
265
+ * | **capped** *(this)* | we could claim more and are choosing not to, pending a policy |
266
+ *
267
+ * Publishing the third as the first **understates a capability we have** — the
268
+ * tier layer would tell a user we cannot do something we deliberately declined
269
+ * to claim. That is §20.2's distinction one layer up, and the same family as the
270
+ * `FILE` gap: two different sentences collapsing into one because there was only
271
+ * one field to put them in.
272
+ *
273
+ * The case that produced it: an introspection-fed route table is R4 on fidelity
274
+ * — it is read from a running process — and is claimed at R3 because R4's
275
+ * promotion and expiry policy is open, R4 requires `observedByRun`, and R4 is
276
+ * unconditionally class A (DEC-115). Nothing is lost, because R3 already reaches
277
+ * class A for a name-level fact; what is lost is the *promise about expiry*, and
278
+ * that is exactly what wants recording.
279
+ *
280
+ * ## It does not waive anything
281
+ *
282
+ * Unlike a `StructuralLimit`, a cap is **not** removed from the tier check. The
283
+ * requirement is met at the lower claim, so there is nothing to waive — and that
284
+ * asymmetry is deliberate: if a cap could skip a requirement it would become the
285
+ * cheapest way to collect a tier, which is the abuse `admissibleLimits` already
286
+ * guards against for the other kind.
287
+ */
288
+ export interface PolicyCap {
289
+ /** A requirement id, or the name of a check. */
290
+ readonly id: string;
291
+ /** What the evidence would support claiming. */
292
+ readonly claimable: string;
293
+ /** What is actually claimed. */
294
+ readonly claimed: string;
295
+ /**
296
+ * The open policy that would lift the cap.
297
+ *
298
+ * **Required, and a cap without one is ignored.** "Capped pending nothing" is
299
+ * not a cap, it is a decision — and a cap that names no condition can never be
300
+ * discharged, so it would sit on the certificate forever looking like caution.
301
+ */
302
+ readonly pendingOn: string;
303
+ /** Who declared it. Same rule as `StructuralLimit`: unattributable is not admissible. */
304
+ readonly declaredBy: string;
305
+ }
306
+ /** A bar the reference set cannot answer, because it holds nothing to sample. */
307
+ export interface EmptyPopulation {
308
+ readonly repository: string;
309
+ readonly edgeType: EdgeType | "mixed";
310
+ }
311
+ export interface UnmetRequirement {
312
+ readonly requirement: Requirement;
313
+ /** Why it is unmet, in terms of what was found rather than what was wanted. */
314
+ readonly because: string;
315
+ }
316
+ export interface TierCertificate {
317
+ readonly adapter: string;
318
+ readonly language: string;
319
+ /** `null` means not even Tier C — and `unmetForNext` says what is missing. */
320
+ readonly tier: Tier | null;
321
+ /** The tier immediately above `tier`, and every requirement it lacks. */
322
+ readonly nextTier: Tier | null;
323
+ readonly unmetForNext: readonly UnmetRequirement[];
324
+ /** Every tier's verdict, so a reader can see the whole ladder at once. */
325
+ readonly byTier: Readonly<Record<Tier, readonly UnmetRequirement[]>>;
326
+ /**
327
+ * Names of the people whose declarations this tier rests on. Empty when the
328
+ * tier needed no attested requirement — C and B do not.
329
+ */
330
+ readonly attestedBy: readonly string[];
331
+ /**
332
+ * Edge types the qualifying precision draws actually covered. Empty under
333
+ * the same condition as `attestedBy`. See the generator's own comment: a
334
+ * tier resting on, say, an `IMPORTS`-only draw has not had `USES_API`
335
+ * itself sampled, and this is where a reader finds that out rather than
336
+ * assuming "Tier A" answered a question it did not.
337
+ */
338
+ readonly attestedEdgeTypes: readonly (EdgeType | "mixed")[];
339
+ /**
340
+ * Requirements waived because this language cannot satisfy them, and checks
341
+ * declared unreachable. **Never empty-by-omission in the printed form** — a
342
+ * consumer that prints the tier without these has published a capability
343
+ * claim the certificate does not make.
344
+ */
345
+ readonly limitations: readonly StructuralLimit[];
346
+ /**
347
+ * Deliberate under-claims, printed for the same reason `limitations` are:
348
+ * omitting them publishes a cap as an absence, which understates a capability
349
+ * the adapter has.
350
+ */
351
+ readonly policyCaps: readonly PolicyCap[];
352
+ /**
353
+ * Bars this **reference set** cannot answer — every recorded draw whose
354
+ * population was zero.
355
+ *
356
+ * Printed beside the structural limitations because it is the same kind of
357
+ * statement: not *"the adapter fell short"* but *"there was nothing here to
358
+ * measure"*. The remedy is other repositories, and a certificate that does not
359
+ * say so sends a reader to measure harder against an empty population.
360
+ */
361
+ readonly unreachableHere: readonly EmptyPopulation[];
362
+ /** DEC-075 §6.3: does `capabilities()` match what was emitted? */
363
+ readonly capabilitiesHonest: boolean | "unknown";
364
+ }
365
+ export declare function certify(evidence: GateEvidence): TierCertificate;
366
+ /**
367
+ * The certificate as a developer should read it: the tier, then immediately the
368
+ * reason it is not higher.
369
+ *
370
+ * The failing rows are not an appendix. A support matrix that prints "B" and
371
+ * stops teaches a reader that B is the ceiling; printing what A is missing
372
+ * turns the same fact into a work item.
373
+ */
374
+ export declare function formatCertificate(certificate: TierCertificate): string;
375
+ //# sourceMappingURL=certify.d.ts.map