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,415 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { z } from 'zod';
3
+ import { parse, pixelRectSchema } from '../entities/contracts.js';
4
+ import { ClassifierError, assertActive } from '../entities/errors.js';
5
+ import { wholeObjectSchema } from '../entities/whole-object.js';
6
+ import { cropMask, maskedRgba, validateMask } from '../features/segmentation/mask.js';
7
+ import { validateSegmentationBoxCheckResult } from '../features/segmentation/box-check.js';
8
+ import { assertRasterImage, contains, validateAndSnapshotRgba, validateSegmentationResult, } from './extract.js';
9
+ const decisionMetadataSchema = z
10
+ .object({
11
+ model: z.string().nullable(),
12
+ provider: z.string().nullable(),
13
+ requestId: z.string().nullable(),
14
+ durationMs: z.number().finite().nonnegative(),
15
+ costUsd: z.number().finite().nonnegative().nullable(),
16
+ attempts: z.number().int().positive(),
17
+ })
18
+ .strict();
19
+ const selectionSchema = z
20
+ .object({
21
+ choice: z.string().min(1),
22
+ nativeChoice: z.string().nullable(),
23
+ probabilities: z.record(z.number().finite()).nullable(),
24
+ ranking: z.array(z.string().min(1)).optional(),
25
+ confidence: z.number().finite().optional(),
26
+ probabilityRoundingStep: z.literal(0.01).optional(),
27
+ tied: z.array(z.string().min(1)),
28
+ })
29
+ .strict();
30
+ const normalizedRectSchema = z
31
+ .object({
32
+ left: z.number().finite().min(0).max(1),
33
+ top: z.number().finite().min(0).max(1),
34
+ width: z.number().finite().positive().max(1),
35
+ height: z.number().finite().positive().max(1),
36
+ })
37
+ .strict();
38
+ const localizationMetadataSchema = z
39
+ .object({
40
+ image: z
41
+ .object({
42
+ width: z.number().int().positive(),
43
+ height: z.number().int().positive(),
44
+ coordinateSpace: z.literal('oriented-source-pixels'),
45
+ })
46
+ .strict(),
47
+ stages: z.array(decisionMetadataSchema),
48
+ durationMs: z.number().finite().nonnegative(),
49
+ knownCostUsd: z.number().finite().nonnegative(),
50
+ unknownCostCount: z.number().int().nonnegative(),
51
+ })
52
+ .strict();
53
+ const refinementSchema = z.discriminatedUnion('status', [
54
+ z.object({ status: z.literal('refined'), region: pixelRectSchema, method: z.string() }).strict(),
55
+ z.object({ status: z.literal('unknown'), reason: z.string() }).strict(),
56
+ ]);
57
+ const locatedSeedSchema = z
58
+ .object({
59
+ status: z.literal('located'),
60
+ region: pixelRectSchema,
61
+ normalizedRegion: normalizedRectSchema,
62
+ selection: selectionSchema,
63
+ method: z.literal('three-choice-grid-v1'),
64
+ refinement: refinementSchema.optional(),
65
+ metadata: localizationMetadataSchema,
66
+ })
67
+ .strict();
68
+ /** Original region seed, full-source mask, closed containment check and at most one repair. */
69
+ export async function extractFromRegion(input, dependencies) {
70
+ const started = performance.now();
71
+ const ownedBytes = new Uint8Array(input.image);
72
+ const { signal } = input;
73
+ let source;
74
+ let image;
75
+ try {
76
+ const parsed = parse(wholeObjectSchema, {
77
+ image: ownedBytes,
78
+ target: input.target,
79
+ exclusions: input.exclusions,
80
+ ...(input.roi ? { roi: input.roi } : {}),
81
+ ...(input.representation ? { representation: input.representation } : {}),
82
+ ...(input.boxTolerancePx !== undefined ? { boxTolerancePx: input.boxTolerancePx } : {}),
83
+ ...(input.maxExtentPasses !== undefined ? { maxExtentPasses: input.maxExtentPasses } : {}),
84
+ mode: 'whole-object',
85
+ });
86
+ assertActive(signal);
87
+ const normalized = await dependencies.raster.normalize(ownedBytes, signal);
88
+ assertRasterImage(normalized);
89
+ source = { ...normalized, bytes: new Uint8Array(normalized.bytes) };
90
+ assertActive(signal);
91
+ const roi = parsed.roi ?? { left: 0, top: 0, width: source.width, height: source.height };
92
+ if (!contains({ left: 0, top: 0, width: source.width, height: source.height }, roi))
93
+ throw new ClassifierError('INVALID_INPUT', 'ROI lies outside oriented source image');
94
+ const rawLocation = await dependencies.locateRegion({ ...source, bytes: new Uint8Array(source.bytes) }, {
95
+ target: parsed.target,
96
+ exclusions: [...parsed.exclusions],
97
+ ...(parsed.roi ? { roi: { ...parsed.roi } } : {}),
98
+ ...(parsed.representation ? { representation: { ...parsed.representation } } : {}),
99
+ signal,
100
+ });
101
+ assertActive(signal);
102
+ if (rawLocation.status !== 'located')
103
+ return withLocalizationDuration(rawLocation, started, source);
104
+ const seedLocation = snapshotSeedLocation(rawLocation, source, roi);
105
+ const rawRgba = await dependencies.image.rgba(source, signal);
106
+ assertActive(signal);
107
+ image = validateAndSnapshotRgba(rawRgba, source.width, source.height);
108
+ if (!contains({ left: 0, top: 0, width: image.width, height: image.height }, seedLocation.region) ||
109
+ !contains(roi, seedLocation.region))
110
+ throw new ClassifierError('INVALID_RESPONSE', 'Region locator returned invalid source geometry');
111
+ const segmentInput = {
112
+ image,
113
+ seedRegion: { ...seedLocation.region },
114
+ prompt: 'seed-region',
115
+ wholeRegion: { ...roi },
116
+ bounds: boundsFor(roi),
117
+ roi: { ...roi },
118
+ target: parsed.target,
119
+ exclusions: [...parsed.exclusions],
120
+ completeness: 'visible-object',
121
+ signal,
122
+ };
123
+ let segmented = await segmentSource(dependencies.segmenter, segmentInput);
124
+ if (segmented.status === 'unknown') {
125
+ const metadata = withMetadataDuration(seedLocation.metadata, started, source);
126
+ return {
127
+ status: 'unknown',
128
+ reason: 'segmentation-uncertain',
129
+ seedLocation: { ...seedLocation, metadata },
130
+ metadata,
131
+ segmentation: segmented.metadata,
132
+ };
133
+ }
134
+ const ownedMasks = [segmented.mask];
135
+ const verification = { checks: [], expansion: 'not-needed' };
136
+ try {
137
+ let box = validateSourceMask(segmented.mask, image, roi, seedLocation.region);
138
+ if (!box) {
139
+ const metadata = withMetadataDuration(seedLocation.metadata, started, source);
140
+ return {
141
+ status: 'unknown',
142
+ reason: 'segmentation-uncertain',
143
+ seedLocation: { ...seedLocation, metadata },
144
+ metadata,
145
+ segmentation: segmented.metadata,
146
+ };
147
+ }
148
+ clearTransparentForeground(segmented.mask, image);
149
+ let check = await checkBox(dependencies.boxCheck, segmentInput, box);
150
+ verification.checks.push(check);
151
+ if (check.verdict === 'needs-expansion') {
152
+ if (!dependencies.segmenter.expand) {
153
+ verification.expansion = 'unsupported';
154
+ return unresolved(seedLocation, segmented.metadata, verification, started, source);
155
+ }
156
+ const expanded = await segmentSource(dependencies.segmenter, segmentInput, segmented.mask);
157
+ if (expanded.status === 'unknown') {
158
+ verification.expansion = 'no-enclosing-mask';
159
+ return unresolved(seedLocation, expanded.metadata, verification, started, source);
160
+ }
161
+ ownedMasks.push(expanded.mask);
162
+ const expandedBox = validateSourceMask(expanded.mask, image, roi, seedLocation.region);
163
+ clearTransparentForeground(expanded.mask, image);
164
+ if (!expandedBox || !isEnclosingMask(segmented.mask, box, expanded.mask, expandedBox)) {
165
+ verification.expansion = 'rejected-mask';
166
+ return unresolved(seedLocation, expanded.metadata, verification, started, source);
167
+ }
168
+ verification.expansion = 'expanded';
169
+ segmented = expanded;
170
+ box = expandedBox;
171
+ check = await checkBox(dependencies.boxCheck, segmentInput, box);
172
+ verification.checks.push(check);
173
+ }
174
+ if (check.verdict !== 'complete')
175
+ return unresolved(seedLocation, segmented.metadata, verification, started, source);
176
+ const croppedMask = cropMask(segmented.mask, box);
177
+ const rgba = maskedRgba(image, segmented.mask, box);
178
+ let rawPng;
179
+ try {
180
+ rawPng = await dependencies.image.png({ data: rgba, width: box.width, height: box.height, format: 'rgba8' }, signal);
181
+ }
182
+ catch (error) {
183
+ if (error instanceof ClassifierError)
184
+ throw error;
185
+ throw new ClassifierError('PROVIDER', 'Cutout raster PNG encoder failed');
186
+ }
187
+ finally {
188
+ rgba.fill(0);
189
+ }
190
+ assertActive(signal);
191
+ if (!(rawPng instanceof Uint8Array) ||
192
+ rawPng.byteLength < 8 ||
193
+ ![137, 80, 78, 71, 13, 10, 26, 10].every((byte, index) => rawPng[index] === byte))
194
+ throw new ClassifierError('INVALID_RESPONSE', 'Cutout raster port returned invalid PNG bytes');
195
+ const metadata = withMetadataDuration(seedLocation.metadata, started, source, verification);
196
+ const location = locatedFromMask(seedLocation, box, image, roi, metadata);
197
+ location.verification = verification;
198
+ return {
199
+ status: 'extracted',
200
+ location,
201
+ png: new Uint8Array(rawPng),
202
+ mask: croppedMask,
203
+ region: { ...box },
204
+ center: { x: box.left + box.width / 2, y: box.top + box.height / 2 },
205
+ offset: { x: box.left, y: box.top },
206
+ segmentation: segmented.metadata,
207
+ verification,
208
+ };
209
+ }
210
+ finally {
211
+ for (const mask of ownedMasks)
212
+ mask.data.fill(0);
213
+ }
214
+ }
215
+ finally {
216
+ ownedBytes.fill(0);
217
+ source?.bytes.fill(0);
218
+ image?.data.fill(0);
219
+ }
220
+ }
221
+ async function segmentSource(segmenter, input, previous) {
222
+ const image = { ...input.image, data: new Uint8Array(input.image.data) };
223
+ const previousMask = previous ? { ...previous, data: new Uint8Array(previous.data) } : undefined;
224
+ const owned = {
225
+ ...input,
226
+ image,
227
+ seedRegion: { ...input.seedRegion },
228
+ wholeRegion: { ...input.wholeRegion },
229
+ roi: { ...input.roi },
230
+ bounds: {
231
+ left: { ...input.bounds.left },
232
+ right: { ...input.bounds.right },
233
+ top: { ...input.bounds.top },
234
+ bottom: { ...input.bounds.bottom },
235
+ },
236
+ exclusions: [...input.exclusions],
237
+ };
238
+ try {
239
+ assertActive(input.signal);
240
+ const result = previousMask
241
+ ? await segmenter.expand?.(owned, previousMask)
242
+ : await segmenter.segment(owned);
243
+ assertActive(input.signal);
244
+ return validateSegmentationResult(result, image.width, image.height);
245
+ }
246
+ finally {
247
+ image.data.fill(0);
248
+ previousMask?.data.fill(0);
249
+ }
250
+ }
251
+ async function checkBox(port, input, box) {
252
+ const image = { ...input.image, data: new Uint8Array(input.image.data) };
253
+ try {
254
+ assertActive(input.signal);
255
+ const result = await port.check({
256
+ image,
257
+ region: { ...box },
258
+ seedRegion: { ...input.seedRegion },
259
+ roi: { ...input.roi },
260
+ target: input.target,
261
+ exclusions: [...input.exclusions],
262
+ signal: input.signal,
263
+ });
264
+ assertActive(input.signal);
265
+ return validateSegmentationBoxCheckResult(result);
266
+ }
267
+ finally {
268
+ image.data.fill(0);
269
+ }
270
+ }
271
+ function validateSourceMask(mask, image, roi, seed) {
272
+ try {
273
+ return validateMask(mask, image, roi, seed);
274
+ }
275
+ catch {
276
+ throw new ClassifierError('INVALID_RESPONSE', 'Segmenter mask failed binary/source validation');
277
+ }
278
+ }
279
+ function clearTransparentForeground(mask, image) {
280
+ for (let index = 0; index < mask.data.length; index++)
281
+ if (image.data[index * 4 + 3] === 0)
282
+ mask.data[index] = 0;
283
+ }
284
+ function isEnclosingMask(previous, previousBox, expanded, box) {
285
+ if (!contains(box, previousBox) ||
286
+ (box.left === previousBox.left &&
287
+ box.top === previousBox.top &&
288
+ box.width === previousBox.width &&
289
+ box.height === previousBox.height))
290
+ return false;
291
+ let added = false;
292
+ for (let i = 0; i < previous.data.length; i++) {
293
+ if (previous.data[i] === 1 && expanded.data[i] !== 1)
294
+ return false;
295
+ if (previous.data[i] !== 1 && expanded.data[i] === 1)
296
+ added = true;
297
+ }
298
+ return added;
299
+ }
300
+ function unresolved(seedLocation, segmentation, verification, started, source) {
301
+ const metadata = withMetadataDuration(seedLocation.metadata, started, source, verification);
302
+ return {
303
+ status: 'unknown',
304
+ reason: 'segmentation-uncertain',
305
+ seedLocation: { ...seedLocation, metadata },
306
+ metadata,
307
+ segmentation,
308
+ verification,
309
+ };
310
+ }
311
+ function snapshotSeedLocation(value, source, roi) {
312
+ const parsed = locatedSeedSchema.safeParse(value);
313
+ if (!parsed.success)
314
+ throw new ClassifierError('INVALID_RESPONSE', 'Region locator returned an invalid result', {
315
+ paths: parsed.error.issues.map((issue) => issue.path.join('.')),
316
+ });
317
+ const seed = parsed.data;
318
+ if (seed.metadata.image.width !== source.width ||
319
+ seed.metadata.image.height !== source.height ||
320
+ !contains({ left: 0, top: 0, width: source.width, height: source.height }, seed.region) ||
321
+ !contains(roi, seed.region))
322
+ throw new ClassifierError('INVALID_RESPONSE', 'Region locator returned invalid source geometry');
323
+ return {
324
+ status: 'located',
325
+ region: { ...seed.region },
326
+ normalizedRegion: { ...seed.normalizedRegion },
327
+ selection: {
328
+ choice: seed.selection.choice,
329
+ nativeChoice: seed.selection.nativeChoice,
330
+ probabilities: seed.selection.probabilities ? { ...seed.selection.probabilities } : null,
331
+ ...(seed.selection.ranking ? { ranking: [...seed.selection.ranking] } : {}),
332
+ ...(seed.selection.confidence !== undefined ? { confidence: seed.selection.confidence } : {}),
333
+ ...(seed.selection.probabilityRoundingStep !== undefined
334
+ ? { probabilityRoundingStep: seed.selection.probabilityRoundingStep }
335
+ : {}),
336
+ tied: [...seed.selection.tied],
337
+ },
338
+ ...(seed.refinement !== undefined ? { refinement: { ...seed.refinement } } : {}),
339
+ method: 'three-choice-grid-v1',
340
+ metadata: snapshotLocalizationMetadata(seed.metadata),
341
+ };
342
+ }
343
+ function snapshotLocalizationMetadata(value) {
344
+ const parsed = localizationMetadataSchema.safeParse(value);
345
+ if (!parsed.success)
346
+ throw new ClassifierError('INVALID_RESPONSE', 'Region locator returned invalid metadata', {
347
+ paths: parsed.error.issues.map((issue) => issue.path.join('.')),
348
+ });
349
+ return {
350
+ ...parsed.data,
351
+ image: { ...parsed.data.image },
352
+ stages: parsed.data.stages.map((stage) => ({ ...stage })),
353
+ };
354
+ }
355
+ function withLocalizationDuration(value, started, source) {
356
+ const metadata = withMetadataDuration(value.metadata, started, source);
357
+ return { ...value, metadata };
358
+ }
359
+ function withMetadataDuration(value, started, source, verification) {
360
+ const metadata = snapshotLocalizationMetadata(value);
361
+ if (source && (metadata.image.width !== source.width || metadata.image.height !== source.height))
362
+ throw new ClassifierError('INVALID_RESPONSE', 'Region locator metadata source dimensions mismatch');
363
+ metadata.durationMs = Math.max(0, performance.now() - started);
364
+ for (const check of verification?.checks ?? []) {
365
+ metadata.stages.push({ ...check.metadata });
366
+ if (check.metadata.costUsd === null)
367
+ metadata.unknownCostCount++;
368
+ else
369
+ metadata.knownCostUsd += check.metadata.costUsd;
370
+ }
371
+ return metadata;
372
+ }
373
+ function locatedFromMask(seed, box, image, roi, metadata) {
374
+ const touchesRoi = box.left === roi.left ||
375
+ box.top === roi.top ||
376
+ box.left + box.width === roi.left + roi.width ||
377
+ box.top + box.height === roi.top + roi.height;
378
+ return {
379
+ status: 'located',
380
+ kind: 'whole-object',
381
+ seedRegion: { ...seed.region },
382
+ region: { ...box },
383
+ normalizedRegion: {
384
+ left: box.left / image.width,
385
+ top: box.top / image.height,
386
+ width: box.width / image.width,
387
+ height: box.height / image.height,
388
+ },
389
+ bounds: boundsForMask(box),
390
+ completeness: touchesRoi ? 'boundary-limited' : 'visible-object',
391
+ method: 'region-checked-segmentation-v1',
392
+ center: { x: box.left + box.width / 2, y: box.top + box.height / 2 },
393
+ metadata,
394
+ };
395
+ }
396
+ function boundsFor(roi) {
397
+ const right = roi.left + roi.width;
398
+ const bottom = roi.top + roi.height;
399
+ return {
400
+ left: { start: roi.left, endExclusive: right },
401
+ right: { start: roi.left, endExclusive: right },
402
+ top: { start: roi.top, endExclusive: bottom },
403
+ bottom: { start: roi.top, endExclusive: bottom },
404
+ };
405
+ }
406
+ function boundsForMask(box) {
407
+ const right = box.left + box.width;
408
+ const bottom = box.top + box.height;
409
+ return {
410
+ left: { start: box.left, endExclusive: box.left + 1 },
411
+ right: { start: right - 1, endExclusive: right },
412
+ top: { start: box.top, endExclusive: box.top + 1 },
413
+ bottom: { start: bottom - 1, endExclusive: bottom },
414
+ };
415
+ }
@@ -0,0 +1,25 @@
1
+ import type { PixelRect, RasterImage, RasterPort } from '../entities/contracts.js';
2
+ import type { CutoutRasterPort } from '../entities/cutout-raster.js';
3
+ import type { ExtractionResult, SegmentationPort, SegmentationResult } from '../entities/segmentation.js';
4
+ import type { RgbaRaster } from '../entities/representation.js';
5
+ import type { WholeObjectLocateInput, WholeObjectLocalizationResult } from '../entities/whole-object.js';
6
+ type WholeObjectLocator = (image: RasterImage, request: Omit<WholeObjectLocateInput, 'image' | 'mode'> & {
7
+ signal: AbortSignal;
8
+ }) => Promise<WholeObjectLocalizationResult>;
9
+ interface ExtractDependencies {
10
+ raster: RasterPort;
11
+ image: CutoutRasterPort;
12
+ segmenter: SegmentationPort;
13
+ locateWhole: WholeObjectLocator;
14
+ }
15
+ /** Runs whole-object location and explicit segmentation over one normalized source image. */
16
+ export declare function extractObject(input: Omit<WholeObjectLocateInput, 'mode' | 'signal'> & {
17
+ mode?: 'whole-object';
18
+ signal: AbortSignal;
19
+ }, dependencies: ExtractDependencies): Promise<ExtractionResult>;
20
+ /** Validates and owns a segmenter's result before any subsequent asynchronous work. */
21
+ export declare function validateSegmentationResult(value: unknown, width: number, height: number): SegmentationResult;
22
+ export declare function assertRasterImage(image: RasterImage): void;
23
+ export declare function validateAndSnapshotRgba(value: unknown, width: number, height: number): RgbaRaster;
24
+ export declare function contains(outer: PixelRect, inner: PixelRect): boolean;
25
+ export {};
@@ -0,0 +1,223 @@
1
+ import { z } from 'zod';
2
+ import { parse } from '../entities/contracts.js';
3
+ import { ClassifierError, assertActive } from '../entities/errors.js';
4
+ import { wholeObjectSchema } from '../entities/whole-object.js';
5
+ import { cropMask, maskedRgba, validateMask } from '../features/segmentation/mask.js';
6
+ const binaryMaskSchema = z
7
+ .object({
8
+ data: z.instanceof(Uint8Array),
9
+ width: z.number().int().positive(),
10
+ height: z.number().int().positive(),
11
+ format: z.literal('binary-u8'),
12
+ })
13
+ .strict();
14
+ const rgbaSchema = z
15
+ .object({
16
+ data: z.instanceof(Uint8Array),
17
+ width: z.number().int().positive().max(16384),
18
+ height: z.number().int().positive().max(16384),
19
+ format: z.literal('rgba8'),
20
+ })
21
+ .strict();
22
+ const segmentationMetadataSchema = z
23
+ .object({
24
+ method: z.string().min(1).max(128),
25
+ backend: z.string().min(1).max(128),
26
+ durationMs: z.number().finite().nonnegative(),
27
+ input: z
28
+ .object({ width: z.number().int().positive(), height: z.number().int().positive() })
29
+ .strict(),
30
+ qualityScore: z.number().finite().optional(),
31
+ })
32
+ .strict();
33
+ const segmentationResultSchema = z.discriminatedUnion('status', [
34
+ z
35
+ .object({
36
+ status: z.literal('segmented'),
37
+ mask: binaryMaskSchema,
38
+ metadata: segmentationMetadataSchema,
39
+ })
40
+ .strict(),
41
+ z
42
+ .object({
43
+ status: z.literal('unknown'),
44
+ reason: z.string().min(1).max(500),
45
+ metadata: segmentationMetadataSchema,
46
+ })
47
+ .strict(),
48
+ ]);
49
+ /** Runs whole-object location and explicit segmentation over one normalized source image. */
50
+ export async function extractObject(input, dependencies) {
51
+ const ownedBytes = new Uint8Array(input.image);
52
+ const parsed = parse(wholeObjectSchema, {
53
+ image: ownedBytes,
54
+ target: input.target,
55
+ exclusions: input.exclusions,
56
+ ...(input.roi ? { roi: input.roi } : {}),
57
+ ...(input.representation ? { representation: input.representation } : {}),
58
+ ...(input.boxTolerancePx !== undefined ? { boxTolerancePx: input.boxTolerancePx } : {}),
59
+ ...(input.maxExtentPasses !== undefined ? { maxExtentPasses: input.maxExtentPasses } : {}),
60
+ mode: 'whole-object',
61
+ });
62
+ const { signal } = input;
63
+ assertActive(signal);
64
+ const normalized = await dependencies.raster.normalize(ownedBytes, signal);
65
+ assertRasterImage(normalized);
66
+ const source = { ...normalized, bytes: new Uint8Array(normalized.bytes) };
67
+ assertActive(signal);
68
+ const location = await dependencies.locateWhole({ ...source, bytes: new Uint8Array(source.bytes) }, {
69
+ target: parsed.target,
70
+ exclusions: parsed.exclusions,
71
+ ...(parsed.roi ? { roi: parsed.roi } : {}),
72
+ ...(parsed.representation ? { representation: parsed.representation } : {}),
73
+ boxTolerancePx: parsed.boxTolerancePx,
74
+ maxExtentPasses: parsed.maxExtentPasses,
75
+ signal,
76
+ });
77
+ assertActive(signal);
78
+ if (location.status !== 'located')
79
+ return location;
80
+ const rawImage = await dependencies.image.rgba(source, signal);
81
+ assertActive(signal);
82
+ const image = validateAndSnapshotRgba(rawImage, source.width, source.height);
83
+ const roi = parsed.roi ?? { left: 0, top: 0, width: image.width, height: image.height };
84
+ if (!contains({ left: 0, top: 0, width: image.width, height: image.height }, location.region) ||
85
+ !contains(roi, location.region) ||
86
+ !contains({ left: 0, top: 0, width: image.width, height: image.height }, location.seedRegion) ||
87
+ !intersects(location.region, location.seedRegion) ||
88
+ !intersects(roi, location.seedRegion))
89
+ throw new ClassifierError('INVALID_RESPONSE', 'Whole-object locator returned invalid source geometry');
90
+ const segmentInput = {
91
+ image: { ...image, data: new Uint8Array(image.data) },
92
+ seedRegion: { ...location.seedRegion },
93
+ wholeRegion: { ...location.region },
94
+ bounds: {
95
+ left: { ...location.bounds.left },
96
+ right: { ...location.bounds.right },
97
+ top: { ...location.bounds.top },
98
+ bottom: { ...location.bounds.bottom },
99
+ },
100
+ roi: { ...roi },
101
+ target: parsed.target,
102
+ exclusions: [...parsed.exclusions],
103
+ completeness: location.completeness,
104
+ signal,
105
+ };
106
+ const rawResult = await dependencies.segmenter.segment(segmentInput);
107
+ assertActive(signal);
108
+ const result = validateSegmentationResult(rawResult, image.width, image.height);
109
+ if (result.status === 'unknown')
110
+ return {
111
+ status: 'unknown',
112
+ reason: 'segmentation-uncertain',
113
+ location,
114
+ segmentation: result.metadata,
115
+ };
116
+ let box;
117
+ try {
118
+ box = validateMask(result.mask, image, roi, location.seedRegion);
119
+ }
120
+ catch {
121
+ throw new ClassifierError('INVALID_RESPONSE', 'Segmenter mask failed binary/source validation');
122
+ }
123
+ if (!box)
124
+ return {
125
+ status: 'unknown',
126
+ reason: 'segmentation-uncertain',
127
+ location,
128
+ segmentation: result.metadata,
129
+ };
130
+ for (let index = 0; index < result.mask.data.length; index++)
131
+ if (image.data[index * 4 + 3] === 0)
132
+ result.mask.data[index] = 0;
133
+ const croppedMask = cropMask(result.mask, box);
134
+ const rgba = maskedRgba(image, result.mask, box);
135
+ let rawPng;
136
+ try {
137
+ rawPng = await dependencies.image.png({ data: rgba, width: box.width, height: box.height, format: 'rgba8' }, signal);
138
+ }
139
+ catch (error) {
140
+ if (error instanceof ClassifierError)
141
+ throw error;
142
+ throw new ClassifierError('PROVIDER', 'Cutout raster PNG encoder failed');
143
+ }
144
+ assertActive(signal);
145
+ if (!(rawPng instanceof Uint8Array) ||
146
+ rawPng.byteLength < 8 ||
147
+ ![137, 80, 78, 71, 13, 10, 26, 10].every((byte, index) => rawPng[index] === byte))
148
+ throw new ClassifierError('INVALID_RESPONSE', 'Cutout raster port returned invalid PNG bytes');
149
+ const png = new Uint8Array(rawPng);
150
+ return {
151
+ status: 'extracted',
152
+ location,
153
+ png,
154
+ mask: croppedMask,
155
+ region: box,
156
+ center: { x: box.left + box.width / 2, y: box.top + box.height / 2 },
157
+ offset: { x: box.left, y: box.top },
158
+ segmentation: result.metadata,
159
+ };
160
+ }
161
+ /** Validates and owns a segmenter's result before any subsequent asynchronous work. */
162
+ export function validateSegmentationResult(value, width, height) {
163
+ const validation = segmentationResultSchema.safeParse(value);
164
+ if (!validation.success)
165
+ throw new ClassifierError('INVALID_RESPONSE', 'Segmenter returned an invalid result', {
166
+ paths: validation.error.issues.map((issue) => issue.path.join('.')),
167
+ });
168
+ if (validation.data.metadata.input.width !== width ||
169
+ validation.data.metadata.input.height !== height)
170
+ throw new ClassifierError('INVALID_RESPONSE', 'Segmenter metadata dimensions do not match the source image');
171
+ return validation.data.status === 'segmented'
172
+ ? {
173
+ status: 'segmented',
174
+ mask: { ...validation.data.mask, data: new Uint8Array(validation.data.mask.data) },
175
+ metadata: snapshotMetadata(validation.data.metadata),
176
+ }
177
+ : {
178
+ ...validation.data,
179
+ metadata: snapshotMetadata(validation.data.metadata),
180
+ };
181
+ }
182
+ export function assertRasterImage(image) {
183
+ if (!Number.isSafeInteger(image.width) ||
184
+ !Number.isSafeInteger(image.height) ||
185
+ image.width < 1 ||
186
+ image.height < 1 ||
187
+ image.width * image.height > 16_777_216 ||
188
+ image.bytes.byteLength < 1)
189
+ throw new ClassifierError('INVALID_RESPONSE', 'Raster normalizer returned invalid source dimensions');
190
+ }
191
+ export function validateAndSnapshotRgba(value, width, height) {
192
+ const image = parse(rgbaSchema, value, 'INVALID_RESPONSE');
193
+ if (image.format !== 'rgba8' ||
194
+ image.width !== width ||
195
+ image.height !== height ||
196
+ !(image.data instanceof Uint8Array) ||
197
+ image.data.length !== width * height * 4)
198
+ throw new ClassifierError('INVALID_RESPONSE', 'Cutout raster port returned invalid RGBA dimensions');
199
+ return { data: new Uint8Array(image.data), width, height, format: 'rgba8' };
200
+ }
201
+ export function contains(outer, inner) {
202
+ return (inner.left >= outer.left &&
203
+ inner.top >= outer.top &&
204
+ inner.width > 0 &&
205
+ inner.height > 0 &&
206
+ inner.left + inner.width <= outer.left + outer.width &&
207
+ inner.top + inner.height <= outer.top + outer.height);
208
+ }
209
+ function intersects(first, second) {
210
+ return (first.left < second.left + second.width &&
211
+ second.left < first.left + first.width &&
212
+ first.top < second.top + second.height &&
213
+ second.top < first.top + first.height);
214
+ }
215
+ function snapshotMetadata(metadata) {
216
+ return {
217
+ method: metadata.method,
218
+ backend: metadata.backend,
219
+ durationMs: metadata.durationMs,
220
+ input: { ...metadata.input },
221
+ ...(metadata.qualityScore !== undefined ? { qualityScore: metadata.qualityScore } : {}),
222
+ };
223
+ }
@@ -1,2 +1,3 @@
1
1
  import type { DecisionPort, LocateInput, LocalizationResult, RasterPort, RefinementPort } from '../entities/contracts.js';
2
- export declare function locateInput(input: Omit<LocateInput, 'signal'>, decision: DecisionPort, raster: RasterPort, signal: AbortSignal, refiner?: RefinementPort): Promise<LocalizationResult>;
2
+ import type { ImageRepresentationPort } from '../entities/representation.js';
3
+ export declare function locateInput(input: Omit<LocateInput, 'signal'>, decision: DecisionPort, raster: RasterPort, signal: AbortSignal, refiner?: RefinementPort, representation?: ImageRepresentationPort): Promise<LocalizationResult>;