zorya-classifier 0.1.0 → 0.2.1

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 (311) hide show
  1. package/NOTICE.md +2 -0
  2. package/README.md +37 -9
  3. package/dist/app/cli-arguments.d.ts +40 -0
  4. package/dist/app/cli-arguments.js +67 -0
  5. package/dist/app/create-classifier.d.ts +24 -0
  6. package/dist/app/create-classifier.js +226 -14
  7. package/dist/app/extraction-files.d.ts +9 -0
  8. package/dist/app/extraction-files.js +28 -0
  9. package/dist/cli.js +58 -14
  10. package/dist/entities/boundary-view.d.ts +29 -0
  11. package/dist/entities/boundary-view.js +1 -0
  12. package/dist/entities/contracts.d.ts +81 -1
  13. package/dist/entities/contracts.js +7 -0
  14. package/dist/entities/cutout-raster.d.ts +7 -0
  15. package/dist/entities/cutout-raster.js +1 -0
  16. package/dist/entities/object-proposals.d.ts +56 -0
  17. package/dist/entities/object-proposals.js +8 -0
  18. package/dist/entities/representation.d.ts +225 -0
  19. package/dist/entities/representation.js +33 -0
  20. package/dist/entities/segmentation-metadata.d.ts +11 -0
  21. package/dist/entities/segmentation-metadata.js +1 -0
  22. package/dist/entities/segmentation-verification.d.ts +31 -0
  23. package/dist/entities/segmentation-verification.js +1 -0
  24. package/dist/entities/segmentation.d.ts +164 -0
  25. package/dist/entities/segmentation.js +5 -0
  26. package/dist/entities/whole-object.d.ts +132 -0
  27. package/dist/entities/whole-object.js +7 -0
  28. package/dist/features/choice.d.ts +1 -1
  29. package/dist/features/choice.js +2 -2
  30. package/dist/features/segmentation/box-check.d.ts +10 -0
  31. package/dist/features/segmentation/box-check.js +299 -0
  32. package/dist/features/segmentation/mask.d.ts +6 -0
  33. package/dist/features/segmentation/mask.js +89 -0
  34. package/dist/features/segmentation/packed-mask.d.ts +5 -0
  35. package/dist/features/segmentation/packed-mask.js +53 -0
  36. package/dist/index.d.ts +6 -1
  37. package/dist/infrastructure/boundary-view.d.ts +2 -0
  38. package/dist/infrastructure/boundary-view.js +263 -0
  39. package/dist/infrastructure/openrouter-common.d.ts +8 -8
  40. package/dist/infrastructure/openrouter-vision.d.ts +3 -0
  41. package/dist/infrastructure/openrouter-vision.js +5 -0
  42. package/dist/infrastructure/raster.d.ts +10 -1
  43. package/dist/infrastructure/raster.js +99 -4
  44. package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
  45. package/dist/infrastructure/segmentation/box-presentation.js +136 -0
  46. package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
  47. package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
  48. package/dist/infrastructure/segmentation/grabcut/index.d.ts +17 -0
  49. package/dist/infrastructure/segmentation/grabcut/index.js +121 -0
  50. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
  51. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
  52. package/dist/infrastructure/segmentation/index.d.ts +2 -0
  53. package/dist/infrastructure/segmentation/index.js +2 -0
  54. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +66 -0
  55. package/dist/infrastructure/segmentation/mobile-sam/index.js +478 -0
  56. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
  57. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
  58. package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
  59. package/dist/infrastructure/segmentation/native-child.js +297 -0
  60. package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
  61. package/dist/infrastructure/segmentation/native-protocol.js +242 -0
  62. package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
  63. package/dist/infrastructure/segmentation/native-worker.js +93 -0
  64. package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
  65. package/dist/infrastructure/segmentation/probe-backend.js +31 -0
  66. package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
  67. package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
  68. package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
  69. package/dist/infrastructure/segmentation/verify-asset.js +32 -0
  70. package/dist/infrastructure/text-representation.d.ts +23 -0
  71. package/dist/infrastructure/text-representation.js +226 -0
  72. package/dist/infrastructure/vision-chat.d.ts +1 -1
  73. package/dist/infrastructure/vision-chat.js +13 -4
  74. package/dist/openai-compatible.d.ts +3 -0
  75. package/dist/openai-compatible.js +4 -0
  76. package/dist/openrouter.d.ts +2 -2
  77. package/dist/openrouter.js +1 -1
  78. package/dist/processes/boundary-state.d.ts +12 -0
  79. package/dist/processes/boundary-state.js +196 -0
  80. package/dist/processes/classify.d.ts +2 -1
  81. package/dist/processes/classify.js +21 -3
  82. package/dist/processes/extract-from-region.d.ts +19 -0
  83. package/dist/processes/extract-from-region.js +415 -0
  84. package/dist/processes/extract.d.ts +25 -0
  85. package/dist/processes/extract.js +223 -0
  86. package/dist/processes/locate.d.ts +2 -1
  87. package/dist/processes/locate.js +84 -11
  88. package/dist/processes/proposal-location.d.ts +19 -0
  89. package/dist/processes/proposal-location.js +396 -0
  90. package/dist/processes/whole-object.d.ts +7 -0
  91. package/dist/processes/whole-object.js +272 -0
  92. package/dist/segmentation.d.ts +10 -0
  93. package/dist/segmentation.js +4 -0
  94. package/dist/shared/input-budget.d.ts +7 -0
  95. package/dist/shared/input-budget.js +21 -0
  96. package/docs/0.2.0-plan.md +190 -0
  97. package/docs/README.ru.md +43 -14
  98. package/docs/acceptance.md +116 -3
  99. package/docs/api.md +12 -6
  100. package/docs/architecture.md +22 -7
  101. package/docs/benchmarks.md +10 -0
  102. package/docs/categorical-presence-port.md +26 -0
  103. package/docs/evidence/v02/baseline.json +1043 -0
  104. package/docs/evidence/v02/boundary-variants.md +17 -0
  105. package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
  106. package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
  107. package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
  108. package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
  109. package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
  110. package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
  111. package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
  112. package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
  113. package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
  114. package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
  115. package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
  116. package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
  117. package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
  118. package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
  119. package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
  120. package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
  121. package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
  122. package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
  123. package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
  124. package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
  125. package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
  126. package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
  127. package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
  128. package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
  129. package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
  130. package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
  131. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
  132. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
  133. package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
  134. package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
  135. package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
  136. package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
  137. package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
  138. package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
  139. package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
  140. package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
  141. package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
  142. package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
  143. package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
  144. package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
  145. package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
  146. package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
  147. package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
  148. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
  149. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
  150. package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
  151. package/docs/evidence/v02/ci-012b935.json +17 -0
  152. package/docs/evidence/v02/ci-0c365cb.json +372 -0
  153. package/docs/evidence/v02/ci-185bde8.json +32 -0
  154. package/docs/evidence/v02/ci-24f849f.json +95 -0
  155. package/docs/evidence/v02/ci-25c19cd.json +375 -0
  156. package/docs/evidence/v02/ci-2922caf.json +375 -0
  157. package/docs/evidence/v02/ci-29357d7.json +29 -0
  158. package/docs/evidence/v02/ci-32aa90d.json +24 -0
  159. package/docs/evidence/v02/ci-35e3064.json +376 -0
  160. package/docs/evidence/v02/ci-4331b1b.json +384 -0
  161. package/docs/evidence/v02/ci-43b7426.json +376 -0
  162. package/docs/evidence/v02/ci-450439c.json +373 -0
  163. package/docs/evidence/v02/ci-46283fe.json +29 -0
  164. package/docs/evidence/v02/ci-4c9c09f.json +34 -0
  165. package/docs/evidence/v02/ci-5579646.json +27 -0
  166. package/docs/evidence/v02/ci-6f9cc15.json +127 -0
  167. package/docs/evidence/v02/ci-7c1051f.json +27 -0
  168. package/docs/evidence/v02/ci-9837888.json +34 -0
  169. package/docs/evidence/v02/ci-987ac32.json +29 -0
  170. package/docs/evidence/v02/ci-9d00fe5.json +25 -0
  171. package/docs/evidence/v02/ci-9f85acc.json +127 -0
  172. package/docs/evidence/v02/ci-be275a6.json +201 -0
  173. package/docs/evidence/v02/ci-c107ddf.json +372 -0
  174. package/docs/evidence/v02/ci-c35f67d.json +127 -0
  175. package/docs/evidence/v02/ci-c5eddd8.json +32 -0
  176. package/docs/evidence/v02/ci-d2042e0.json +372 -0
  177. package/docs/evidence/v02/ci-d684259.json +375 -0
  178. package/docs/evidence/v02/ci-e0c190b.json +127 -0
  179. package/docs/evidence/v02/ci-e551253.json +372 -0
  180. package/docs/evidence/v02/ci-f9d4633.json +29 -0
  181. package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
  182. package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
  183. package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
  184. package/docs/evidence/v02/fresh-development.md +16 -0
  185. package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
  186. package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
  187. package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
  188. package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
  189. package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
  190. package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
  191. package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
  192. package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
  193. package/docs/evidence/v02/fresh-text/README.md +23 -0
  194. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
  195. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
  196. package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
  197. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
  198. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
  199. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
  200. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
  201. package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
  202. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
  203. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
  204. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
  205. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
  206. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
  207. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
  208. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
  209. package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
  210. package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
  211. package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
  212. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
  213. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
  214. package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
  215. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
  216. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
  217. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
  218. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
  219. package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
  220. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
  221. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
  222. package/docs/evidence/v02/fresh-text/report.json +284 -0
  223. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
  224. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
  225. package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
  226. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
  227. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
  228. package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
  229. package/docs/evidence/v02/heldout-input-derivation.md +18 -0
  230. package/docs/evidence/v02/integration-0c365cb.json +90 -0
  231. package/docs/evidence/v02/integration-25c19cd.json +52 -0
  232. package/docs/evidence/v02/integration-35e3064.json +41 -0
  233. package/docs/evidence/v02/integration-4331b1b.json +42 -0
  234. package/docs/evidence/v02/integration-4c9c09f.json +46 -0
  235. package/docs/evidence/v02/integration-8010aad.json +48 -0
  236. package/docs/evidence/v02/integration-c1aa640.json +41 -0
  237. package/docs/evidence/v02/integration-eea1790.json +46 -0
  238. package/docs/evidence/v02/integration-fa82e65.json +42 -0
  239. package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
  240. package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
  241. package/docs/evidence/v02/local-archive-cleanup.json +59 -0
  242. package/docs/evidence/v02/native-base64-after.json +30 -0
  243. package/docs/evidence/v02/native-base64-before.json +32 -0
  244. package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
  245. package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
  246. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
  247. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
  248. package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
  249. package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
  250. package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
  251. package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
  252. package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
  253. package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
  254. package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
  255. package/docs/evidence/v02/native-windows-eea1790.json +228 -0
  256. package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
  257. package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
  258. package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
  259. package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
  260. package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
  261. package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
  262. package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
  263. package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
  264. package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
  265. package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
  266. package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
  267. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
  268. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
  269. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
  270. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
  271. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
  272. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
  273. package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
  274. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
  275. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
  276. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
  277. package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
  278. package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
  279. package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
  280. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
  281. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
  282. package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
  283. package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
  284. package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
  285. package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
  286. package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
  287. package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
  288. package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
  289. package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
  290. package/docs/evidence/v02/seed-outline/summary.json +487 -0
  291. package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
  292. package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
  293. package/docs/evidence/v02/segmentation/README.md +21 -0
  294. package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
  295. package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
  296. package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
  297. package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
  298. package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
  299. package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
  300. package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
  301. package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
  302. package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
  303. package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
  304. package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
  305. package/docs/operations.md +8 -2
  306. package/docs/plan.md +2 -0
  307. package/docs/publishing.md +16 -1
  308. package/docs/research/text-transport-audit.md +66 -0
  309. package/docs/v02-usage.md +164 -0
  310. package/docs/vision-results.md +31 -2
  311. package/package.json +30 -5
@@ -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
+ }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,9 @@ export { createClassifier } from './app/create-classifier.js';
2
2
  export type { Classifier, ClassifierOptions } from './app/create-classifier.js';
3
3
  export { ClassifierError } from './entities/errors.js';
4
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, } from './entities/contracts.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';
@@ -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
+ }
@@ -85,6 +85,13 @@ export declare const requestSchema: z.ZodObject<{
85
85
  instructions: string;
86
86
  }>>;
87
87
  }, "strict", z.ZodTypeAny, {
88
+ state: (string | {
89
+ type: "image_url";
90
+ image_url: {
91
+ url: string;
92
+ detail: "high";
93
+ };
94
+ })[];
88
95
  questions: Record<string, {
89
96
  type: "choice";
90
97
  instructions: string;
@@ -94,6 +101,7 @@ export declare const requestSchema: z.ZodObject<{
94
101
  type: "noul";
95
102
  instructions: string;
96
103
  }>;
104
+ }, {
97
105
  state: (string | {
98
106
  type: "image_url";
99
107
  image_url: {
@@ -101,7 +109,6 @@ export declare const requestSchema: z.ZodObject<{
101
109
  detail: "high";
102
110
  };
103
111
  })[];
104
- }, {
105
112
  questions: Record<string, {
106
113
  type: "choice";
107
114
  instructions: string;
@@ -111,13 +118,6 @@ export declare const requestSchema: z.ZodObject<{
111
118
  type: "noul";
112
119
  instructions: string;
113
120
  }>;
114
- state: (string | {
115
- type: "image_url";
116
- image_url: {
117
- url: string;
118
- detail: "high";
119
- };
120
- })[];
121
121
  }>;
122
122
  export declare function ownedError(code: ErrorCode, message: string, details?: Readonly<Record<string, unknown>>): ClassifierError;
123
123
  export declare function invalid(code: 'INVALID_INPUT' | 'INVALID_RESPONSE', message: string): ClassifierError;
@@ -6,3 +6,6 @@ export interface OpenRouterVisionOptions extends Omit<VisionChatOptions, 'endpoi
6
6
  }
7
7
  /** Optional OpenRouter routing wrapper over the ordinary chat-completions adapter. */
8
8
  export declare function createOpenRouterVision(input: OpenRouterVisionOptions): DecisionPort;
9
+ export type OpenRouterTextOptions = OpenRouterVisionOptions;
10
+ /** Same strict chat protocol; rejects image parts before transport. */
11
+ export declare function createOpenRouterText(input: OpenRouterTextOptions): DecisionPort;
@@ -10,3 +10,8 @@ export function createOpenRouterVision(input) {
10
10
  const options = validated(schema, input, 'INVALID_INPUT');
11
11
  return createVisionChat({ ...options, endpoint: 'https://openrouter.ai/api/v1/chat/completions' });
12
12
  }
13
+ /** Same strict chat protocol; rejects image parts before transport. */
14
+ export function createOpenRouterText(input) {
15
+ const options = validated(schema, input, 'INVALID_INPUT');
16
+ return createVisionChat({ ...options, endpoint: 'https://openrouter.ai/api/v1/chat/completions' }, 'text');
17
+ }
@@ -1,2 +1,11 @@
1
- import type { RasterPort } from '../entities/contracts.js';
1
+ import type { RasterImage, RasterPort } from '../entities/contracts.js';
2
+ import type { ImageRepresentationPort } from '../entities/representation.js';
2
3
  export declare function createSharpRaster(): RasterPort;
4
+ /** Decode an oriented raster into the canonical RGBA8 pixel layout used by adapters. */
5
+ export declare function readCanonicalPixels(image: RasterImage, signal: AbortSignal): Promise<{
6
+ data: Uint8Array;
7
+ width: number;
8
+ height: number;
9
+ }>;
10
+ /** Vision adapter that retains the existing sharp imageState layout for every supplied view. */
11
+ export declare function createVisionRepresentation(raster: RasterPort): ImageRepresentationPort;
@@ -1,5 +1,6 @@
1
1
  import sharp from 'sharp';
2
- import { limits } from '../entities/contracts.js';
2
+ import { z } from 'zod';
3
+ import { limits, parse, pixelRectSchema } from '../entities/contracts.js';
3
4
  import { ClassifierError, assertActive } from '../entities/errors.js';
4
5
  function contains(outer, inner) {
5
6
  return (inner.left >= outer.left &&
@@ -15,10 +16,16 @@ async function normalizeImage(bytes, signal) {
15
16
  assertActive(signal);
16
17
  if (!['png', 'jpeg', 'webp'].includes(metadata.format ?? '') || (metadata.pages ?? 1) !== 1)
17
18
  throw new ClassifierError('INVALID_INPUT', 'Use a single PNG, JPEG or WebP image');
18
- const normalized = await pipeline.rotate().png().toBuffer({ resolveWithObject: true });
19
+ const normalized = await pipeline
20
+ .rotate()
21
+ .ensureAlpha()
22
+ .png()
23
+ .toBuffer({ resolveWithObject: true });
19
24
  assertActive(signal);
20
- if (normalized.data.byteLength > limits.imageBytes)
21
- throw new ClassifierError('RESOURCE_LIMIT', 'Decoded presentation exceeds image byte limit');
25
+ if (normalized.data.byteLength > limits.imageBytes ||
26
+ normalized.info.width > 16384 ||
27
+ normalized.info.height > 16384)
28
+ throw new ClassifierError('RESOURCE_LIMIT', 'Decoded source exceeds image byte or side limits');
22
29
  return { bytes: normalized.data, width: normalized.info.width, height: normalized.info.height };
23
30
  }
24
31
  catch (error) {
@@ -119,3 +126,91 @@ async function renderGrid(image, region, cells, labels, signal) {
119
126
  export function createSharpRaster() {
120
127
  return { normalize: normalizeImage, crop: cropImage, imageState, grid: renderGrid };
121
128
  }
129
+ /** Decode an oriented raster into the canonical RGBA8 pixel layout used by adapters. */
130
+ export async function readCanonicalPixels(image, signal) {
131
+ assertActive(signal);
132
+ if (!Number.isSafeInteger(image.width) ||
133
+ !Number.isSafeInteger(image.height) ||
134
+ image.width < 1 ||
135
+ image.height < 1 ||
136
+ image.width > 16384 ||
137
+ image.height > 16384 ||
138
+ image.width * image.height > limits.imagePixels)
139
+ throw new ClassifierError('RESOURCE_LIMIT', 'Raster dimensions exceed pixel limits');
140
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels, failOn: 'error' })
141
+ .rotate()
142
+ .ensureAlpha()
143
+ .raw();
144
+ try {
145
+ const result = await pipeline.toBuffer({ resolveWithObject: true });
146
+ assertActive(signal);
147
+ if (result.info.width !== image.width ||
148
+ result.info.height !== image.height ||
149
+ result.info.channels !== 4 ||
150
+ result.data.byteLength !== image.width * image.height * 4)
151
+ throw new ClassifierError('INVALID_INPUT', 'Raster dimensions do not match decoded pixels');
152
+ return { data: result.data, width: result.info.width, height: result.info.height };
153
+ }
154
+ catch (error) {
155
+ assertActive(signal);
156
+ if (error instanceof ClassifierError)
157
+ throw error;
158
+ throw new ClassifierError('INVALID_INPUT', 'Raster cannot be decoded as canonical RGBA8');
159
+ }
160
+ finally {
161
+ pipeline.destroy();
162
+ }
163
+ }
164
+ /** Vision adapter that retains the existing sharp imageState layout for every supplied view. */
165
+ export function createVisionRepresentation(raster) {
166
+ return {
167
+ async prepare(input) {
168
+ assertActive(input.signal);
169
+ if (input.views.length > 9)
170
+ throw new ClassifierError('RESOURCE_LIMIT', 'A representation request may contain at most nine views', {
171
+ component: 'representation',
172
+ });
173
+ const state = [];
174
+ const metadata = { profile: 'vision', bytes: 0, views: [] };
175
+ for (const view of input.views) {
176
+ assertActive(input.signal);
177
+ const parts = await raster.imageState(view.image, input.signal);
178
+ state.push(...parts);
179
+ const imagePart = parts.find((part) => typeof part !== 'string');
180
+ const transformPart = parts.find((part) => typeof part === 'string');
181
+ if (imagePart?.type !== 'image_url' || !transformPart)
182
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster did not produce an image and transform');
183
+ const payload = imagePart.image_url.url.slice('data:image/png;base64,'.length);
184
+ metadata.bytes += Buffer.from(payload, 'base64').byteLength;
185
+ let json;
186
+ try {
187
+ json = JSON.parse(transformPart);
188
+ }
189
+ catch {
190
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster returned invalid transform JSON');
191
+ }
192
+ const transform = parse(z.object({
193
+ sourceImageTransform: z.object({
194
+ output: z.object({
195
+ width: z.number().int().positive().max(16384),
196
+ height: z.number().int().positive().max(16384),
197
+ }),
198
+ content: pixelRectSchema,
199
+ }),
200
+ }), json, 'INVALID_RESPONSE');
201
+ if (!contains({ left: 0, top: 0, ...transform.sourceImageTransform.output }, transform.sourceImageTransform.content))
202
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster content lies outside output');
203
+ metadata.views.push({
204
+ id: view.id,
205
+ sourceRegion: view.sourceRegion,
206
+ width: transform.sourceImageTransform.output.width,
207
+ height: transform.sourceImageTransform.output.height,
208
+ scaleX: transform.sourceImageTransform.content.width / view.sourceRegion.width,
209
+ scaleY: transform.sourceImageTransform.content.height / view.sourceRegion.height,
210
+ content: transform.sourceImageTransform.content,
211
+ });
212
+ }
213
+ return { state, metadata };
214
+ },
215
+ };
216
+ }
@@ -0,0 +1,2 @@
1
+ import type { SegmentationBoxPresentationPort } from '../../entities/segmentation-verification.js';
2
+ export declare function createSharpSegmentationBoxPresentation(): SegmentationBoxPresentationPort;