zorya-classifier 0.0.0-stage → 0.2.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 (369) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +139 -2
  4. package/dist/app/cli-arguments.d.ts +40 -0
  5. package/dist/app/cli-arguments.js +67 -0
  6. package/dist/app/cli-route.d.ts +3 -0
  7. package/dist/app/cli-route.js +40 -0
  8. package/dist/app/create-classifier.d.ts +33 -0
  9. package/dist/app/create-classifier.js +312 -0
  10. package/dist/app/extraction-files.d.ts +9 -0
  11. package/dist/app/extraction-files.js +28 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +77 -0
  14. package/dist/entities/boundary-view.d.ts +29 -0
  15. package/dist/entities/boundary-view.js +1 -0
  16. package/dist/entities/contracts.d.ts +471 -0
  17. package/dist/entities/contracts.js +97 -0
  18. package/dist/entities/cutout-raster.d.ts +7 -0
  19. package/dist/entities/cutout-raster.js +1 -0
  20. package/dist/entities/errors.d.ts +7 -0
  21. package/dist/entities/errors.js +16 -0
  22. package/dist/entities/object-proposals.d.ts +53 -0
  23. package/dist/entities/object-proposals.js +8 -0
  24. package/dist/entities/representation.d.ts +225 -0
  25. package/dist/entities/representation.js +33 -0
  26. package/dist/entities/segmentation-metadata.d.ts +11 -0
  27. package/dist/entities/segmentation-metadata.js +1 -0
  28. package/dist/entities/segmentation-verification.d.ts +30 -0
  29. package/dist/entities/segmentation-verification.js +1 -0
  30. package/dist/entities/segmentation.d.ts +159 -0
  31. package/dist/entities/segmentation.js +5 -0
  32. package/dist/entities/whole-object.d.ts +128 -0
  33. package/dist/entities/whole-object.js +7 -0
  34. package/dist/features/choice-beam-layout.d.ts +18 -0
  35. package/dist/features/choice-beam-layout.js +136 -0
  36. package/dist/features/choice.d.ts +4 -0
  37. package/dist/features/choice.js +83 -0
  38. package/dist/features/prompts.d.ts +8 -0
  39. package/dist/features/prompts.js +56 -0
  40. package/dist/features/segmentation/box-check.d.ts +10 -0
  41. package/dist/features/segmentation/box-check.js +299 -0
  42. package/dist/features/segmentation/mask.d.ts +6 -0
  43. package/dist/features/segmentation/mask.js +89 -0
  44. package/dist/features/segmentation/packed-mask.d.ts +5 -0
  45. package/dist/features/segmentation/packed-mask.js +53 -0
  46. package/dist/features/uniform-grid.d.ts +3 -0
  47. package/dist/features/uniform-grid.js +35 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.js +2 -0
  50. package/dist/infrastructure/boundary-view.d.ts +2 -0
  51. package/dist/infrastructure/boundary-view.js +263 -0
  52. package/dist/infrastructure/openrouter-common.d.ts +130 -0
  53. package/dist/infrastructure/openrouter-common.js +317 -0
  54. package/dist/infrastructure/openrouter-vision.d.ts +11 -0
  55. package/dist/infrastructure/openrouter-vision.js +17 -0
  56. package/dist/infrastructure/openrouter.d.ts +19 -0
  57. package/dist/infrastructure/openrouter.js +207 -0
  58. package/dist/infrastructure/raster.d.ts +11 -0
  59. package/dist/infrastructure/raster.js +216 -0
  60. package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
  61. package/dist/infrastructure/segmentation/box-presentation.js +136 -0
  62. package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
  63. package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
  64. package/dist/infrastructure/segmentation/grabcut/index.d.ts +13 -0
  65. package/dist/infrastructure/segmentation/grabcut/index.js +118 -0
  66. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
  67. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
  68. package/dist/infrastructure/segmentation/index.d.ts +2 -0
  69. package/dist/infrastructure/segmentation/index.js +2 -0
  70. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +58 -0
  71. package/dist/infrastructure/segmentation/mobile-sam/index.js +472 -0
  72. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
  73. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
  74. package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
  75. package/dist/infrastructure/segmentation/native-child.js +297 -0
  76. package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
  77. package/dist/infrastructure/segmentation/native-protocol.js +242 -0
  78. package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
  79. package/dist/infrastructure/segmentation/native-worker.js +93 -0
  80. package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
  81. package/dist/infrastructure/segmentation/probe-backend.js +31 -0
  82. package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
  83. package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
  84. package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
  85. package/dist/infrastructure/segmentation/verify-asset.js +32 -0
  86. package/dist/infrastructure/text-representation.d.ts +23 -0
  87. package/dist/infrastructure/text-representation.js +226 -0
  88. package/dist/infrastructure/vision-chat.d.ts +64 -0
  89. package/dist/infrastructure/vision-chat.js +318 -0
  90. package/dist/openai-compatible.d.ts +8 -0
  91. package/dist/openai-compatible.js +11 -0
  92. package/dist/openrouter.d.ts +4 -0
  93. package/dist/openrouter.js +2 -0
  94. package/dist/processes/boundary-state.d.ts +12 -0
  95. package/dist/processes/boundary-state.js +196 -0
  96. package/dist/processes/classify.d.ts +3 -0
  97. package/dist/processes/classify.js +39 -0
  98. package/dist/processes/extract-from-region.d.ts +19 -0
  99. package/dist/processes/extract-from-region.js +415 -0
  100. package/dist/processes/extract.d.ts +25 -0
  101. package/dist/processes/extract.js +223 -0
  102. package/dist/processes/locate.d.ts +3 -0
  103. package/dist/processes/locate.js +274 -0
  104. package/dist/processes/proposal-location.d.ts +19 -0
  105. package/dist/processes/proposal-location.js +396 -0
  106. package/dist/processes/whole-object.d.ts +7 -0
  107. package/dist/processes/whole-object.js +272 -0
  108. package/dist/segmentation.d.ts +10 -0
  109. package/dist/segmentation.js +4 -0
  110. package/dist/shared/choice-mass.d.ts +2 -0
  111. package/dist/shared/choice-mass.js +19 -0
  112. package/dist/shared/gate.d.ts +11 -0
  113. package/dist/shared/gate.js +52 -0
  114. package/dist/shared/input-budget.d.ts +7 -0
  115. package/dist/shared/input-budget.js +21 -0
  116. package/dist/shared/scope.d.ts +12 -0
  117. package/dist/shared/scope.js +29 -0
  118. package/docs/0.2.0-plan.md +190 -0
  119. package/docs/README.ru.md +258 -0
  120. package/docs/acceptance.md +144 -0
  121. package/docs/api.md +49 -0
  122. package/docs/architecture.md +45 -0
  123. package/docs/benchmarks.md +63 -0
  124. package/docs/categorical-presence-port.md +26 -0
  125. package/docs/evidence/current-linux-check.json +15 -0
  126. package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
  127. package/docs/evidence/decisions-completion-v1-report.json +1205 -0
  128. package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
  129. package/docs/evidence/decisions-selection-v1-report.json +1457 -0
  130. package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
  131. package/docs/evidence/linux-check.json +20 -0
  132. package/docs/evidence/linux-check.txt +205 -0
  133. package/docs/evidence/live-release-smoke-manifest.json +82 -0
  134. package/docs/evidence/live-release-smoke-provenance.json +9 -0
  135. package/docs/evidence/live-release-smoke-report.json +2136 -0
  136. package/docs/evidence/live-v1-manifest.json +84 -0
  137. package/docs/evidence/live-v1-provenance.json +9 -0
  138. package/docs/evidence/live-v1-report.json +15555 -0
  139. package/docs/evidence/luna-decisions-fine-probe.json +19 -0
  140. package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
  141. package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
  142. package/docs/evidence/pack-check.json +26 -0
  143. package/docs/evidence/protocol-probes.json +61 -0
  144. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
  145. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
  146. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
  147. package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
  148. package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
  149. package/docs/evidence/v02/baseline.json +1043 -0
  150. package/docs/evidence/v02/boundary-variants.md +17 -0
  151. package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
  152. package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
  153. package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
  154. package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
  155. package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
  156. package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
  157. package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
  158. package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
  159. package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
  160. package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
  161. package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
  162. package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
  163. package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
  164. package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
  165. package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
  166. package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
  167. package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
  168. package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
  169. package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
  170. package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
  171. package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
  172. package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
  173. package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
  174. package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
  175. package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
  176. package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
  177. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
  178. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
  179. package/docs/evidence/v02/categorical-heldout-v4/README.md +35 -0
  180. package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
  181. package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
  182. package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
  183. package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
  184. package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
  185. package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
  186. package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
  187. package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
  188. package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
  189. package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
  190. package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
  191. package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
  192. package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
  193. package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
  194. package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
  195. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
  196. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
  197. package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
  198. package/docs/evidence/v02/ci-012b935.json +17 -0
  199. package/docs/evidence/v02/ci-0c365cb.json +372 -0
  200. package/docs/evidence/v02/ci-185bde8.json +32 -0
  201. package/docs/evidence/v02/ci-24f849f.json +95 -0
  202. package/docs/evidence/v02/ci-25c19cd.json +375 -0
  203. package/docs/evidence/v02/ci-2922caf.json +375 -0
  204. package/docs/evidence/v02/ci-29357d7.json +29 -0
  205. package/docs/evidence/v02/ci-32aa90d.json +24 -0
  206. package/docs/evidence/v02/ci-35e3064.json +376 -0
  207. package/docs/evidence/v02/ci-4331b1b.json +384 -0
  208. package/docs/evidence/v02/ci-43b7426.json +376 -0
  209. package/docs/evidence/v02/ci-450439c.json +373 -0
  210. package/docs/evidence/v02/ci-46283fe.json +29 -0
  211. package/docs/evidence/v02/ci-4c9c09f.json +34 -0
  212. package/docs/evidence/v02/ci-5579646.json +27 -0
  213. package/docs/evidence/v02/ci-6f9cc15.json +127 -0
  214. package/docs/evidence/v02/ci-7c1051f.json +27 -0
  215. package/docs/evidence/v02/ci-9837888.json +34 -0
  216. package/docs/evidence/v02/ci-987ac32.json +29 -0
  217. package/docs/evidence/v02/ci-9d00fe5.json +25 -0
  218. package/docs/evidence/v02/ci-9f85acc.json +127 -0
  219. package/docs/evidence/v02/ci-be275a6.json +201 -0
  220. package/docs/evidence/v02/ci-c107ddf.json +372 -0
  221. package/docs/evidence/v02/ci-c35f67d.json +127 -0
  222. package/docs/evidence/v02/ci-c5eddd8.json +32 -0
  223. package/docs/evidence/v02/ci-d2042e0.json +372 -0
  224. package/docs/evidence/v02/ci-d684259.json +375 -0
  225. package/docs/evidence/v02/ci-e0c190b.json +127 -0
  226. package/docs/evidence/v02/ci-e551253.json +372 -0
  227. package/docs/evidence/v02/ci-f9d4633.json +29 -0
  228. package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
  229. package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
  230. package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
  231. package/docs/evidence/v02/fresh-development.md +16 -0
  232. package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
  233. package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
  234. package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
  235. package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
  236. package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
  237. package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
  238. package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
  239. package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
  240. package/docs/evidence/v02/fresh-text/README.md +23 -0
  241. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
  242. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
  243. package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
  244. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
  245. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
  246. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
  247. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
  248. package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
  249. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
  250. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
  251. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
  252. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
  253. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
  254. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
  255. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
  256. package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
  257. package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
  258. package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
  259. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
  260. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
  261. package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
  262. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
  263. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
  264. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
  265. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
  266. package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
  267. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
  268. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
  269. package/docs/evidence/v02/fresh-text/report.json +284 -0
  270. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
  271. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
  272. package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
  273. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
  274. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
  275. package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
  276. package/docs/evidence/v02/heldout-input-derivation.md +18 -0
  277. package/docs/evidence/v02/integration-0c365cb.json +90 -0
  278. package/docs/evidence/v02/integration-25c19cd.json +52 -0
  279. package/docs/evidence/v02/integration-35e3064.json +41 -0
  280. package/docs/evidence/v02/integration-4331b1b.json +42 -0
  281. package/docs/evidence/v02/integration-4c9c09f.json +46 -0
  282. package/docs/evidence/v02/integration-8010aad.json +48 -0
  283. package/docs/evidence/v02/integration-c1aa640.json +41 -0
  284. package/docs/evidence/v02/integration-eea1790.json +46 -0
  285. package/docs/evidence/v02/integration-fa82e65.json +42 -0
  286. package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
  287. package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
  288. package/docs/evidence/v02/local-archive-cleanup.json +59 -0
  289. package/docs/evidence/v02/native-base64-after.json +30 -0
  290. package/docs/evidence/v02/native-base64-before.json +32 -0
  291. package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
  292. package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
  293. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
  294. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
  295. package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
  296. package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
  297. package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
  298. package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
  299. package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
  300. package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
  301. package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
  302. package/docs/evidence/v02/native-windows-eea1790.json +228 -0
  303. package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
  304. package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
  305. package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
  306. package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
  307. package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
  308. package/docs/evidence/v02/proposal-resource-soak-261dc23.md +15 -0
  309. package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
  310. package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
  311. package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
  312. package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
  313. package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
  314. package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
  315. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
  316. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
  317. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
  318. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
  319. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
  320. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
  321. package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
  322. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
  323. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
  324. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
  325. package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
  326. package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
  327. package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
  328. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
  329. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
  330. package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
  331. package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
  332. package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
  333. package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
  334. package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
  335. package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
  336. package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
  337. package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
  338. package/docs/evidence/v02/seed-outline/summary.json +487 -0
  339. package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
  340. package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
  341. package/docs/evidence/v02/segmentation/README.md +21 -0
  342. package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
  343. package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
  344. package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
  345. package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
  346. package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
  347. package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
  348. package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
  349. package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
  350. package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
  351. package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
  352. package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
  353. package/docs/evidence/vision-cost-reconciliation.json +37 -0
  354. package/docs/evidence/vision-model-summary.json +562 -0
  355. package/docs/evidence/vision-selection-v1-manifest.json +95 -0
  356. package/docs/evidence/vision-selection-v1-report.json +4598 -0
  357. package/docs/evidence/vision-selection-v2-manifest.json +95 -0
  358. package/docs/evidence/vision-selection-v2-report.json +1139 -0
  359. package/docs/evidence/windows-native.json +73 -0
  360. package/docs/evidence/windows-secrets.json +46 -0
  361. package/docs/operations.md +21 -0
  362. package/docs/plan.md +27 -0
  363. package/docs/provenance.json +32 -0
  364. package/docs/publishing.md +30 -0
  365. package/docs/research/text-transport-audit.md +66 -0
  366. package/docs/results.md +69 -0
  367. package/docs/v02-usage.md +162 -0
  368. package/docs/vision-results.md +78 -0
  369. package/package.json +114 -4
@@ -0,0 +1,89 @@
1
+ export function validateMask(mask, image, roi, seed) {
2
+ const pixels = image.width * image.height;
3
+ if (!Number.isSafeInteger(pixels) ||
4
+ pixels < 1 ||
5
+ pixels > 16_777_216 ||
6
+ image.data.length !== pixels * 4 ||
7
+ mask.format !== 'binary-u8' ||
8
+ mask.width !== image.width ||
9
+ mask.height !== image.height ||
10
+ mask.data.length !== pixels ||
11
+ !inside(roi, image.width, image.height) ||
12
+ !inside(seed, image.width, image.height) ||
13
+ !intersects(roi, seed))
14
+ throw new TypeError('Segmentation dimensions or bounds do not match the canonical image');
15
+ let left = image.width;
16
+ let top = image.height;
17
+ let right = -1;
18
+ let bottom = -1;
19
+ let intersectsSeed = false;
20
+ for (let y = 0; y < image.height; y++) {
21
+ for (let x = 0; x < image.width; x++) {
22
+ const value = mask.data[y * image.width + x];
23
+ if (value !== 0 && value !== 1)
24
+ throw new TypeError('Mask values must be binary 0 or 1');
25
+ if (value !== 1)
26
+ continue;
27
+ if (x < roi.left || y < roi.top || x >= roi.left + roi.width || y >= roi.top + roi.height)
28
+ throw new TypeError('Mask contains pixels outside the hard ROI');
29
+ const alpha = image.data[(y * image.width + x) * 4 + 3];
30
+ if (alpha === undefined || alpha === 0)
31
+ continue;
32
+ left = Math.min(left, x);
33
+ top = Math.min(top, y);
34
+ right = Math.max(right, x);
35
+ bottom = Math.max(bottom, y);
36
+ if (x >= seed.left &&
37
+ y >= seed.top &&
38
+ x < seed.left + seed.width &&
39
+ y < seed.top + seed.height)
40
+ intersectsSeed = true;
41
+ }
42
+ }
43
+ if (right < left || !intersectsSeed)
44
+ return undefined;
45
+ return { left, top, width: right - left + 1, height: bottom - top + 1 };
46
+ }
47
+ export function cropMask(mask, box) {
48
+ const data = new Uint8Array(box.width * box.height);
49
+ for (let y = 0; y < box.height; y++) {
50
+ const from = (box.top + y) * mask.width + box.left;
51
+ data.set(mask.data.subarray(from, from + box.width), y * box.width);
52
+ }
53
+ return { data, width: box.width, height: box.height, format: 'binary-u8' };
54
+ }
55
+ export function maskedRgba(image, mask, box) {
56
+ const output = new Uint8Array(box.width * box.height * 4);
57
+ for (let y = 0; y < box.height; y++) {
58
+ for (let x = 0; x < box.width; x++) {
59
+ const sourceIndex = (box.top + y) * image.width + box.left + x;
60
+ if (mask.data[sourceIndex] !== 1)
61
+ continue;
62
+ const source = sourceIndex * 4;
63
+ const target = (y * box.width + x) * 4;
64
+ output[target] = image.data[source] ?? 0;
65
+ output[target + 1] = image.data[source + 1] ?? 0;
66
+ output[target + 2] = image.data[source + 2] ?? 0;
67
+ output[target + 3] = image.data[source + 3] ?? 0;
68
+ }
69
+ }
70
+ return output;
71
+ }
72
+ function inside(rect, width, height) {
73
+ return (Number.isSafeInteger(rect.left) &&
74
+ Number.isSafeInteger(rect.top) &&
75
+ Number.isSafeInteger(rect.width) &&
76
+ Number.isSafeInteger(rect.height) &&
77
+ rect.left >= 0 &&
78
+ rect.top >= 0 &&
79
+ rect.width > 0 &&
80
+ rect.height > 0 &&
81
+ rect.left + rect.width <= width &&
82
+ rect.top + rect.height <= height);
83
+ }
84
+ function intersects(first, second) {
85
+ return (first.left < second.left + second.width &&
86
+ second.left < first.left + first.width &&
87
+ first.top < second.top + second.height &&
88
+ second.top < first.top + first.height);
89
+ }
@@ -0,0 +1,5 @@
1
+ import { type PackedBinaryMask } from '../../entities/object-proposals.js';
2
+ /** Pack row-major binary pixels into bytes, least-significant bit first. */
3
+ export declare function packBinaryMask(mask: Uint8Array, width: number, height: number): PackedBinaryMask;
4
+ /** Validate and unpack an owned LSB-first mask into one byte per source pixel. */
5
+ export declare function unpackBinaryMask(mask: PackedBinaryMask): Uint8Array;
@@ -0,0 +1,53 @@
1
+ import { ClassifierError } from '../../entities/errors.js';
2
+ import { objectProposalLimits } from '../../entities/object-proposals.js';
3
+ /** Pack row-major binary pixels into bytes, least-significant bit first. */
4
+ export function packBinaryMask(mask, width, height) {
5
+ const pixels = checkedPixelCount(width, height);
6
+ if (!(mask instanceof Uint8Array) || mask.length !== pixels)
7
+ throw new ClassifierError('INVALID_INPUT', 'Binary mask dimensions do not match its pixels');
8
+ const data = new Uint8Array(Math.ceil(pixels / 8));
9
+ if (data.byteLength > objectProposalLimits.packedBytes)
10
+ throw new ClassifierError('RESOURCE_LIMIT', 'Packed binary mask exceeds its byte limit');
11
+ for (let index = 0; index < pixels; index++) {
12
+ const value = mask[index];
13
+ if (value !== 0 && value !== 1)
14
+ throw new ClassifierError('INVALID_RESPONSE', 'Binary mask pixels must be zero or one');
15
+ if (value === 1)
16
+ data[index >> 3] = (data[index >> 3] ?? 0) | (1 << (index & 7));
17
+ }
18
+ return { data, width, height, format: 'binary-lsb0' };
19
+ }
20
+ /** Validate and unpack an owned LSB-first mask into one byte per source pixel. */
21
+ export function unpackBinaryMask(mask) {
22
+ if (typeof mask !== 'object' ||
23
+ mask === null ||
24
+ mask.format !== 'binary-lsb0' ||
25
+ !(mask.data instanceof Uint8Array))
26
+ throw new ClassifierError('INVALID_RESPONSE', 'Packed mask format is invalid');
27
+ const pixels = checkedPixelCount(mask.width, mask.height, 'INVALID_RESPONSE');
28
+ const expectedBytes = Math.ceil(pixels / 8);
29
+ if (mask.data.length !== expectedBytes || mask.data.byteLength > objectProposalLimits.packedBytes)
30
+ throw new ClassifierError('INVALID_RESPONSE', 'Packed mask length does not match its dimensions');
31
+ const unusedBits = expectedBytes * 8 - pixels;
32
+ if (unusedBits > 0) {
33
+ const allowed = (1 << (8 - unusedBits)) - 1;
34
+ const last = mask.data[expectedBytes - 1];
35
+ if (last === undefined || (last & ~allowed) !== 0)
36
+ throw new ClassifierError('INVALID_RESPONSE', 'Packed mask has nonzero padding bits');
37
+ }
38
+ const output = new Uint8Array(pixels);
39
+ for (let index = 0; index < pixels; index++)
40
+ output[index] = ((mask.data[index >> 3] ?? 0) >> (index & 7)) & 1;
41
+ return output;
42
+ }
43
+ function checkedPixelCount(width, height, errorCode = 'INVALID_INPUT') {
44
+ const pixels = width * height;
45
+ if (!Number.isSafeInteger(width) ||
46
+ !Number.isSafeInteger(height) ||
47
+ width < 1 ||
48
+ height < 1 ||
49
+ !Number.isSafeInteger(pixels) ||
50
+ pixels > objectProposalLimits.imagePixels)
51
+ throw new ClassifierError(errorCode, 'Binary mask dimensions exceed the proposal limit');
52
+ return pixels;
53
+ }
@@ -0,0 +1,3 @@
1
+ import type { PixelRect } from '../entities/contracts.js';
2
+ import type { GridCell } from '../entities/contracts.js';
3
+ export declare function uniformCoordinateCells(region: PixelRect, columns: number, rows: number): GridCell[];
@@ -0,0 +1,35 @@
1
+ import { pixelRectSchema } from '../entities/contracts.js';
2
+ export function uniformCoordinateCells(region, columns, rows) {
3
+ if (!pixelRectSchema.safeParse(region).success ||
4
+ !Number.isSafeInteger(region.left + region.width) ||
5
+ !Number.isSafeInteger(region.top + region.height) ||
6
+ !Number.isSafeInteger(columns) ||
7
+ !Number.isSafeInteger(rows) ||
8
+ columns < 1 ||
9
+ rows < 1 ||
10
+ columns > 63 ||
11
+ rows > 63)
12
+ throw new RangeError('Uniform grid requires safe bounded coordinates and axis counts');
13
+ const cx = Math.min(columns, region.width), cy = Math.min(rows, region.height);
14
+ if (cx * cy > 713 || cx + cy + 2 > 128)
15
+ throw new RangeError('Uniform grid exceeds its cell or question budget');
16
+ return Array.from({ length: cx * cy }, (_, index) => {
17
+ const column = index % cx, row = Math.floor(index / cx);
18
+ const left = region.left + Math.floor((column * region.width) / cx);
19
+ const top = region.top + Math.floor((row * region.height) / cy);
20
+ return {
21
+ id: `y${row}:x${column}`,
22
+ xId: `x${column}`,
23
+ yId: `y${row}`,
24
+ column,
25
+ row,
26
+ kind: 'cell',
27
+ region: {
28
+ left,
29
+ top,
30
+ width: region.left + Math.floor(((column + 1) * region.width) / cx) - left,
31
+ height: region.top + Math.floor(((row + 1) * region.height) / cy) - top,
32
+ },
33
+ };
34
+ });
35
+ }
@@ -0,0 +1,10 @@
1
+ export { createClassifier } from './app/create-classifier.js';
2
+ export type { Classifier, ClassifierOptions } from './app/create-classifier.js';
3
+ export { ClassifierError } from './entities/errors.js';
4
+ export type { ErrorCode } from './entities/errors.js';
5
+ export type { ClassifyInput, LocateInput, ClassificationResult, LocalizationResult, ClassDefinition, ChoiceSelection, DecisionPort, DecisionRequest, DecisionResult, DecisionState, DecisionMetadata, PixelRect, RefinementPort, RefinementResult, LocalizationMetadata, Representation, } from './entities/contracts.js';
6
+ export type { RegionLocateInput, WholeObjectLocateInput, AnyLocateInput, BoundaryInterval, ObjectBounds, WholeObjectLocated, WholeObjectFailure, WholeObjectLocalizationResult, } from './entities/whole-object.js';
7
+ export type { RgbaRaster, RepresentationView, RepresentationMetadata, ImageRepresentationPort, } from './entities/representation.js';
8
+ export type { BinaryMask, SegmentationInput, SegmentationMetadata, SegmentationResult, SegmentationPort, ExtractionResult, ExtractInput, } from './entities/segmentation.js';
9
+ export type { SegmentationBoxCheckInput, SegmentationBoxCheckResult, SegmentationBoxVerdict, SegmentationBoxCheckPort, SegmentationBoxPresentationPort, SegmentationVerification, } from './entities/segmentation-verification.js';
10
+ export type { PackedBinaryMask, ObjectProposal, ObjectProposalInput, ObjectProposalBatch, ObjectProposalPort, } from './entities/object-proposals.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createClassifier } from './app/create-classifier.js';
2
+ export { ClassifierError } from './entities/errors.js';
@@ -0,0 +1,2 @@
1
+ import type { BoundaryViewPort } from '../entities/boundary-view.js';
2
+ export declare function createSharpBoundaryView(): BoundaryViewPort;
@@ -0,0 +1,263 @@
1
+ import sharp from 'sharp';
2
+ import { z } from 'zod';
3
+ import { limits, pixelRectSchema } from '../entities/contracts.js';
4
+ import { ClassifierError, assertActive } from '../entities/errors.js';
5
+ const sourceSchema = z
6
+ .object({
7
+ bytes: z
8
+ .custom((v) => v instanceof Uint8Array)
9
+ .refine((v) => v.byteLength > 0 && v.byteLength <= limits.imageBytes),
10
+ width: z.number().int().positive().max(16384),
11
+ height: z.number().int().positive().max(16384),
12
+ })
13
+ .strict();
14
+ const candidateSchema = z
15
+ .object({
16
+ id: z
17
+ .string()
18
+ .min(1)
19
+ .max(128)
20
+ .regex(/^[A-Za-z][A-Za-z0-9_-]*$/),
21
+ display: z
22
+ .string()
23
+ .min(1)
24
+ .max(8)
25
+ .regex(/^[A-Za-z0-9_-]+$/),
26
+ interval: z
27
+ .object({
28
+ start: z.number().int().nonnegative().safe(),
29
+ endExclusive: z.number().int().positive().safe(),
30
+ })
31
+ .strict()
32
+ .refine((v) => v.start < v.endExclusive),
33
+ })
34
+ .strict();
35
+ const inputSchema = z
36
+ .object({
37
+ source: sourceSchema,
38
+ sourceRegion: pixelRectSchema,
39
+ axis: z.enum(['left', 'right', 'top', 'bottom']),
40
+ candidates: z.array(candidateSchema).min(1).max(16),
41
+ signal: z.custom((v) => v instanceof AbortSignal),
42
+ })
43
+ .strict();
44
+ const maxContentSide = 1280;
45
+ const maxOutputSide = 1567;
46
+ const axisGutter = 210;
47
+ const outerGutter = 10;
48
+ const maxRenderedBytes = limits.imageBytes;
49
+ function contained(outer, inner) {
50
+ return (inner.left >= outer.left &&
51
+ inner.top >= outer.top &&
52
+ inner.left + inner.width <= outer.left + outer.width &&
53
+ inner.top + inner.height <= outer.top + outer.height);
54
+ }
55
+ function escapeXml(value) {
56
+ return value.replace(/[&<>"']/gu, (char) => {
57
+ switch (char) {
58
+ case '&':
59
+ return '&amp;';
60
+ case '<':
61
+ return '&lt;';
62
+ case '>':
63
+ return '&gt;';
64
+ case '"':
65
+ return '&quot;';
66
+ default:
67
+ return '&apos;';
68
+ }
69
+ });
70
+ }
71
+ function failInput(message) {
72
+ throw new ClassifierError('INVALID_INPUT', message, { component: 'boundary-view' });
73
+ }
74
+ function validate(input) {
75
+ const parsed = inputSchema.safeParse(input);
76
+ if (!parsed.success)
77
+ failInput('Invalid boundary-view request');
78
+ const value = parsed.data;
79
+ if (value.source.width * value.source.height > limits.imagePixels)
80
+ throw new ClassifierError('RESOURCE_LIMIT', 'Source raster exceeds pixel limits', {
81
+ component: 'boundary-view',
82
+ });
83
+ const full = { left: 0, top: 0, width: value.source.width, height: value.source.height };
84
+ if (!contained(full, value.sourceRegion))
85
+ failInput('Source region lies outside the oriented image');
86
+ const xAxis = value.axis === 'left' || value.axis === 'right';
87
+ const axisSize = xAxis ? value.source.width : value.source.height;
88
+ const axisStart = xAxis ? value.sourceRegion.left : value.sourceRegion.top;
89
+ const axisEnd = axisStart + (xAxis ? value.sourceRegion.width : value.sourceRegion.height);
90
+ const ids = new Set();
91
+ const displays = new Set();
92
+ let priorEnd = -1;
93
+ for (const candidate of value.candidates) {
94
+ if (ids.has(candidate.id) || displays.has(candidate.display))
95
+ failInput('Boundary candidate IDs and display labels must be unique');
96
+ ids.add(candidate.id);
97
+ displays.add(candidate.display);
98
+ if (candidate.interval.endExclusive > axisSize)
99
+ failInput('Boundary interval lies outside the oriented source');
100
+ if (candidate.interval.start < axisStart || candidate.interval.endExclusive > axisEnd)
101
+ failInput('Boundary interval lies outside the source region');
102
+ if (candidate.interval.start < priorEnd)
103
+ failInput('Boundary intervals must be ordered and non-overlapping');
104
+ priorEnd = candidate.interval.endExclusive;
105
+ }
106
+ return value;
107
+ }
108
+ function svgOverlay(axis, candidates, region, contentWidth, contentHeight, contentLeft, contentTop, outputWidth, outputHeight) {
109
+ const vertical = axis === 'left' || axis === 'right';
110
+ const axisLength = vertical ? region.width : region.height;
111
+ const outputLength = vertical ? contentWidth : contentHeight;
112
+ const sourceOffset = vertical ? region.left : region.top;
113
+ const unit = outputLength / axisLength;
114
+ const strokes = [];
115
+ // Presentation is tied to the coordinate dimension, never to the semantic side being located.
116
+ const color = vertical ? '#ff6b5f' : '#49d6a1';
117
+ const title = vertical ? 'X coordinates' : 'Y coordinates';
118
+ strokes.push(vertical
119
+ ? `<text x="${outputWidth - outerGutter - 2}" y="${outerGutter + 10}" text-anchor="end" font-family="DejaVu Sans,sans-serif" font-size="9" fill="#f3f4f6">${title}</text>`
120
+ : `<text x="${outerGutter}" y="${outerGutter + 10}" font-family="DejaVu Sans,sans-serif" font-size="9" fill="#f3f4f6">${title}</text>`);
121
+ const positions = candidates.map((candidate) => {
122
+ const begin = (candidate.interval.start - sourceOffset) * unit;
123
+ const end = (candidate.interval.endExclusive - sourceOffset) * unit;
124
+ return (begin + end) / 2;
125
+ });
126
+ const labelBaselines = vertical
127
+ ? (() => {
128
+ const pitch = 12;
129
+ const lanes = [];
130
+ const rightEdges = [];
131
+ return candidates.map((candidate, index) => {
132
+ const center = contentLeft + (positions[index] ?? 0);
133
+ const width = candidate.display.length * 7 + 6;
134
+ let lane = rightEdges.findIndex((right) => right + 3 <= center - width / 2);
135
+ if (lane < 0) {
136
+ lane = rightEdges.length;
137
+ rightEdges.push(Number.NEGATIVE_INFINITY);
138
+ }
139
+ rightEdges[lane] = center + width / 2;
140
+ lanes[index] = lane;
141
+ return contentTop - 5 - (lane + 1) * pitch;
142
+ });
143
+ })()
144
+ : (() => {
145
+ const min = outerGutter + 10;
146
+ const max = outputHeight - outerGutter - 3;
147
+ const pitch = 12;
148
+ const desired = positions.map((position) => contentTop + position + 4);
149
+ const placed = desired.map((position) => Math.max(min, Math.min(max, position)));
150
+ for (let index = 1; index < placed.length; index++)
151
+ placed[index] = Math.max(placed[index] ?? min, (placed[index - 1] ?? min) + pitch);
152
+ if ((placed.at(-1) ?? max) > max) {
153
+ placed[placed.length - 1] = max;
154
+ for (let index = placed.length - 2; index >= 0; index--)
155
+ placed[index] = Math.min(placed[index] ?? max, (placed[index + 1] ?? max) - pitch);
156
+ }
157
+ return placed;
158
+ })();
159
+ for (const [index, candidate] of candidates.entries()) {
160
+ const start = (candidate.interval.start - sourceOffset) * unit;
161
+ const end = (candidate.interval.endExclusive - sourceOffset) * unit;
162
+ const lo = Math.min(start, end);
163
+ const hi = Math.max(start, end);
164
+ const midpoint = (lo + hi) / 2;
165
+ const label = escapeXml(candidate.display);
166
+ if (vertical) {
167
+ const center = contentLeft + midpoint;
168
+ const halfWidth = candidate.display.length * 3.5 + 3;
169
+ const labelCenter = Math.max(contentLeft + halfWidth, Math.min(contentLeft + contentWidth - halfWidth, center));
170
+ const baseline = labelBaselines[index] ?? contentTop - 17;
171
+ strokes.push(`<rect x="${contentLeft + lo}" y="${contentTop}" width="${Math.max(1, hi - lo)}" height="${contentHeight}" fill="${color}" fill-opacity="0.045"/>`, `<line x1="${contentLeft + start}" y1="${contentTop}" x2="${contentLeft + start}" y2="${contentTop + contentHeight}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<line x1="${contentLeft + end}" y1="${contentTop}" x2="${contentLeft + end}" y2="${contentTop + contentHeight}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<path d="M ${center} ${contentTop} L ${center} ${baseline + 4} L ${labelCenter} ${baseline + 4}" fill="none" stroke="${color}" stroke-width="1"/>`, `<text x="${labelCenter}" y="${baseline}" text-anchor="middle" font-family="DejaVu Sans,sans-serif" font-size="10" font-weight="bold" fill="${color}">${label}</text>`);
172
+ }
173
+ else {
174
+ const midpointY = contentTop + midpoint;
175
+ const baseline = labelBaselines[index] ?? midpointY + 4;
176
+ const elbowX = contentLeft - 12 - index * 3;
177
+ const labelX = contentLeft - 82;
178
+ strokes.push(`<rect x="${contentLeft}" y="${contentTop + lo}" width="${contentWidth}" height="${Math.max(1, hi - lo)}" fill="${color}" fill-opacity="0.045"/>`, `<line x1="${contentLeft}" y1="${contentTop + start}" x2="${contentLeft + contentWidth}" y2="${contentTop + start}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<line x1="${contentLeft}" y1="${contentTop + end}" x2="${contentLeft + contentWidth}" y2="${contentTop + end}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<path d="M ${contentLeft} ${midpointY} L ${elbowX} ${midpointY} L ${elbowX} ${baseline} L ${labelX + 4} ${baseline}" fill="none" stroke="${color}" stroke-width="1"/>`, `<text x="${labelX}" y="${baseline}" text-anchor="end" font-family="DejaVu Sans,sans-serif" font-size="10" font-weight="bold" fill="${color}">${label}</text>`);
179
+ }
180
+ }
181
+ const sourceBounds = `<rect x="${contentLeft}" y="${contentTop}" width="${contentWidth}" height="${contentHeight}" fill="none" stroke="#f3f4f6" stroke-opacity="0.65" stroke-width="1"/>`;
182
+ return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${outputWidth}" height="${outputHeight}">${sourceBounds}${strokes.join('')}</svg>`);
183
+ }
184
+ async function render(input) {
185
+ assertActive(input.signal);
186
+ const value = validate(input);
187
+ assertActive(value.signal);
188
+ // Sharp retains its input through an asynchronous pipeline; take ownership before awaiting.
189
+ const ownedBytes = Buffer.from(value.source.bytes);
190
+ const candidates = value.candidates.map((candidate) => ({
191
+ id: candidate.id,
192
+ display: candidate.display,
193
+ interval: { ...candidate.interval },
194
+ }));
195
+ const { sourceRegion: region } = value;
196
+ const scale = Math.min(maxContentSide / Math.max(region.width, region.height), Math.max(1, 768 / Math.min(region.width, region.height)));
197
+ const contentWidth = Math.max(1, Math.round(region.width * scale));
198
+ const contentHeight = Math.max(1, Math.round(region.height * scale));
199
+ const vertical = value.axis === 'left' || value.axis === 'right';
200
+ const contentLeft = vertical ? outerGutter : axisGutter;
201
+ const contentTop = vertical ? axisGutter : outerGutter;
202
+ const outputWidth = Math.max(280, contentLeft + contentWidth + outerGutter);
203
+ const outputHeight = Math.max(280, contentTop + contentHeight + outerGutter);
204
+ if (outputWidth > maxOutputSide || outputHeight > maxOutputSide)
205
+ throw new ClassifierError('RESOURCE_LIMIT', 'Boundary view exceeds output side limit', {
206
+ component: 'boundary-view',
207
+ });
208
+ const pixels = outputWidth * outputHeight;
209
+ if (pixels > limits.imagePixels)
210
+ throw new ClassifierError('RESOURCE_LIMIT', 'Boundary view exceeds output pixel limit', {
211
+ component: 'boundary-view',
212
+ });
213
+ const overlay = svgOverlay(value.axis, candidates, region, contentWidth, contentHeight, contentLeft, contentTop, outputWidth, outputHeight);
214
+ if (overlay.byteLength > 128 * 1024)
215
+ throw new ClassifierError('RESOURCE_LIMIT', 'Boundary overlay exceeds byte budget', {
216
+ component: 'boundary-view',
217
+ });
218
+ let pipeline;
219
+ try {
220
+ assertActive(value.signal);
221
+ pipeline = sharp(ownedBytes, { limitInputPixels: limits.imagePixels, failOn: 'error' })
222
+ .extract(region)
223
+ .resize(contentWidth, contentHeight, { kernel: 'nearest' })
224
+ .extend({
225
+ left: contentLeft,
226
+ top: contentTop,
227
+ right: outputWidth - contentLeft - contentWidth,
228
+ bottom: outputHeight - contentTop - contentHeight,
229
+ background: { r: 17, g: 24, b: 39, alpha: 1 },
230
+ })
231
+ .composite([{ input: overlay }]);
232
+ const result = await pipeline.png().toBuffer();
233
+ assertActive(value.signal);
234
+ if (result.byteLength > maxRenderedBytes)
235
+ throw new ClassifierError('RESOURCE_LIMIT', 'Rendered boundary view exceeds byte limit', {
236
+ component: 'boundary-view',
237
+ });
238
+ return {
239
+ image: { bytes: result, width: outputWidth, height: outputHeight },
240
+ sourceRegion: { ...region },
241
+ content: { left: contentLeft, top: contentTop, width: contentWidth, height: contentHeight },
242
+ scaleX: contentWidth / region.width,
243
+ scaleY: contentHeight / region.height,
244
+ candidates,
245
+ };
246
+ }
247
+ catch (error) {
248
+ assertActive(value.signal);
249
+ if (error instanceof ClassifierError)
250
+ throw error;
251
+ throw new ClassifierError('INVALID_INPUT', 'Boundary view could not be rendered', {
252
+ component: 'boundary-view',
253
+ });
254
+ }
255
+ finally {
256
+ pipeline?.destroy();
257
+ ownedBytes.fill(0);
258
+ overlay.fill(0);
259
+ }
260
+ }
261
+ export function createSharpBoundaryView() {
262
+ return { render };
263
+ }
@@ -0,0 +1,130 @@
1
+ import { z } from 'zod';
2
+ import { ClassifierError } from '../entities/errors.js';
3
+ import type { ErrorCode } from '../entities/errors.js';
4
+ export declare const requestLimit: number;
5
+ export declare const responseLimit: number;
6
+ export declare const probability: z.ZodNumber;
7
+ export declare const key: z.ZodEffects<z.ZodString, string, string>;
8
+ export declare const requestSchema: z.ZodObject<{
9
+ state: z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
10
+ type: z.ZodLiteral<"image_url">;
11
+ image_url: z.ZodObject<{
12
+ url: z.ZodString;
13
+ detail: z.ZodLiteral<"high">;
14
+ }, "strict", z.ZodTypeAny, {
15
+ url: string;
16
+ detail: "high";
17
+ }, {
18
+ url: string;
19
+ detail: "high";
20
+ }>;
21
+ }, "strict", z.ZodTypeAny, {
22
+ type: "image_url";
23
+ image_url: {
24
+ url: string;
25
+ detail: "high";
26
+ };
27
+ }, {
28
+ type: "image_url";
29
+ image_url: {
30
+ url: string;
31
+ detail: "high";
32
+ };
33
+ }>]>, "many">, (string | {
34
+ type: "image_url";
35
+ image_url: {
36
+ url: string;
37
+ detail: "high";
38
+ };
39
+ })[], (string | {
40
+ type: "image_url";
41
+ image_url: {
42
+ url: string;
43
+ detail: "high";
44
+ };
45
+ })[]>;
46
+ questions: z.ZodEffects<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
47
+ type: z.ZodLiteral<"choice">;
48
+ rankingSize: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<4>]>>;
49
+ instructions: z.ZodString;
50
+ criteria: z.ZodEffects<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodString>, Record<string, string>, Record<string, string>>;
51
+ }, "strict", z.ZodTypeAny, {
52
+ type: "choice";
53
+ instructions: string;
54
+ rankingSize: 1 | 4;
55
+ criteria: Record<string, string>;
56
+ }, {
57
+ type: "choice";
58
+ instructions: string;
59
+ criteria: Record<string, string>;
60
+ rankingSize?: 1 | 4 | undefined;
61
+ }>, z.ZodObject<{
62
+ type: z.ZodLiteral<"noul">;
63
+ instructions: z.ZodString;
64
+ }, "strict", z.ZodTypeAny, {
65
+ type: "noul";
66
+ instructions: string;
67
+ }, {
68
+ type: "noul";
69
+ instructions: string;
70
+ }>]>>, Record<string, {
71
+ type: "choice";
72
+ instructions: string;
73
+ rankingSize: 1 | 4;
74
+ criteria: Record<string, string>;
75
+ } | {
76
+ type: "noul";
77
+ instructions: string;
78
+ }>, Record<string, {
79
+ type: "choice";
80
+ instructions: string;
81
+ criteria: Record<string, string>;
82
+ rankingSize?: 1 | 4 | undefined;
83
+ } | {
84
+ type: "noul";
85
+ instructions: string;
86
+ }>>;
87
+ }, "strict", z.ZodTypeAny, {
88
+ state: (string | {
89
+ type: "image_url";
90
+ image_url: {
91
+ url: string;
92
+ detail: "high";
93
+ };
94
+ })[];
95
+ questions: Record<string, {
96
+ type: "choice";
97
+ instructions: string;
98
+ rankingSize: 1 | 4;
99
+ criteria: Record<string, string>;
100
+ } | {
101
+ type: "noul";
102
+ instructions: string;
103
+ }>;
104
+ }, {
105
+ state: (string | {
106
+ type: "image_url";
107
+ image_url: {
108
+ url: string;
109
+ detail: "high";
110
+ };
111
+ })[];
112
+ questions: Record<string, {
113
+ type: "choice";
114
+ instructions: string;
115
+ criteria: Record<string, string>;
116
+ rankingSize?: 1 | 4 | undefined;
117
+ } | {
118
+ type: "noul";
119
+ instructions: string;
120
+ }>;
121
+ }>;
122
+ export declare function ownedError(code: ErrorCode, message: string, details?: Readonly<Record<string, unknown>>): ClassifierError;
123
+ export declare function invalid(code: 'INVALID_INPUT' | 'INVALID_RESPONSE', message: string): ClassifierError;
124
+ export declare function active(signal: AbortSignal): void;
125
+ export declare function validated<T>(schema: z.ZodType<T, z.ZodTypeDef, unknown>, input: unknown, code: 'INVALID_INPUT' | 'INVALID_RESPONSE'): T;
126
+ export declare function validateImage(url: string): void;
127
+ /** Bounded owned snapshot; schema parsing never receives caller iterators or accessors. */
128
+ export declare function preflightJson(input: unknown, limit: number): unknown;
129
+ export declare function failure(error: unknown, signal: AbortSignal, cleanupFailed?: boolean): ClassifierError;
130
+ export declare function readResponse(response: Response, signal: AbortSignal, limit: number): Promise<string>;