zorya-classifier 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +139 -2
  4. package/dist/app/cli-arguments.d.ts +40 -0
  5. package/dist/app/cli-arguments.js +67 -0
  6. package/dist/app/cli-route.d.ts +3 -0
  7. package/dist/app/cli-route.js +40 -0
  8. package/dist/app/create-classifier.d.ts +33 -0
  9. package/dist/app/create-classifier.js +312 -0
  10. package/dist/app/extraction-files.d.ts +9 -0
  11. package/dist/app/extraction-files.js +28 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +77 -0
  14. package/dist/entities/boundary-view.d.ts +29 -0
  15. package/dist/entities/boundary-view.js +1 -0
  16. package/dist/entities/contracts.d.ts +471 -0
  17. package/dist/entities/contracts.js +97 -0
  18. package/dist/entities/cutout-raster.d.ts +7 -0
  19. package/dist/entities/cutout-raster.js +1 -0
  20. package/dist/entities/errors.d.ts +7 -0
  21. package/dist/entities/errors.js +16 -0
  22. package/dist/entities/object-proposals.d.ts +53 -0
  23. package/dist/entities/object-proposals.js +8 -0
  24. package/dist/entities/representation.d.ts +225 -0
  25. package/dist/entities/representation.js +33 -0
  26. package/dist/entities/segmentation-metadata.d.ts +11 -0
  27. package/dist/entities/segmentation-metadata.js +1 -0
  28. package/dist/entities/segmentation-verification.d.ts +30 -0
  29. package/dist/entities/segmentation-verification.js +1 -0
  30. package/dist/entities/segmentation.d.ts +159 -0
  31. package/dist/entities/segmentation.js +5 -0
  32. package/dist/entities/whole-object.d.ts +128 -0
  33. package/dist/entities/whole-object.js +7 -0
  34. package/dist/features/choice-beam-layout.d.ts +18 -0
  35. package/dist/features/choice-beam-layout.js +136 -0
  36. package/dist/features/choice.d.ts +4 -0
  37. package/dist/features/choice.js +83 -0
  38. package/dist/features/prompts.d.ts +8 -0
  39. package/dist/features/prompts.js +56 -0
  40. package/dist/features/segmentation/box-check.d.ts +10 -0
  41. package/dist/features/segmentation/box-check.js +299 -0
  42. package/dist/features/segmentation/mask.d.ts +6 -0
  43. package/dist/features/segmentation/mask.js +89 -0
  44. package/dist/features/segmentation/packed-mask.d.ts +5 -0
  45. package/dist/features/segmentation/packed-mask.js +53 -0
  46. package/dist/features/uniform-grid.d.ts +3 -0
  47. package/dist/features/uniform-grid.js +35 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.js +2 -0
  50. package/dist/infrastructure/boundary-view.d.ts +2 -0
  51. package/dist/infrastructure/boundary-view.js +263 -0
  52. package/dist/infrastructure/openrouter-common.d.ts +130 -0
  53. package/dist/infrastructure/openrouter-common.js +317 -0
  54. package/dist/infrastructure/openrouter-vision.d.ts +11 -0
  55. package/dist/infrastructure/openrouter-vision.js +17 -0
  56. package/dist/infrastructure/openrouter.d.ts +19 -0
  57. package/dist/infrastructure/openrouter.js +207 -0
  58. package/dist/infrastructure/raster.d.ts +11 -0
  59. package/dist/infrastructure/raster.js +216 -0
  60. package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
  61. package/dist/infrastructure/segmentation/box-presentation.js +136 -0
  62. package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
  63. package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
  64. package/dist/infrastructure/segmentation/grabcut/index.d.ts +13 -0
  65. package/dist/infrastructure/segmentation/grabcut/index.js +118 -0
  66. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
  67. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
  68. package/dist/infrastructure/segmentation/index.d.ts +2 -0
  69. package/dist/infrastructure/segmentation/index.js +2 -0
  70. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +58 -0
  71. package/dist/infrastructure/segmentation/mobile-sam/index.js +472 -0
  72. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
  73. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
  74. package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
  75. package/dist/infrastructure/segmentation/native-child.js +297 -0
  76. package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
  77. package/dist/infrastructure/segmentation/native-protocol.js +242 -0
  78. package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
  79. package/dist/infrastructure/segmentation/native-worker.js +93 -0
  80. package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
  81. package/dist/infrastructure/segmentation/probe-backend.js +31 -0
  82. package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
  83. package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
  84. package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
  85. package/dist/infrastructure/segmentation/verify-asset.js +32 -0
  86. package/dist/infrastructure/text-representation.d.ts +23 -0
  87. package/dist/infrastructure/text-representation.js +226 -0
  88. package/dist/infrastructure/vision-chat.d.ts +64 -0
  89. package/dist/infrastructure/vision-chat.js +318 -0
  90. package/dist/openai-compatible.d.ts +8 -0
  91. package/dist/openai-compatible.js +11 -0
  92. package/dist/openrouter.d.ts +4 -0
  93. package/dist/openrouter.js +2 -0
  94. package/dist/processes/boundary-state.d.ts +12 -0
  95. package/dist/processes/boundary-state.js +196 -0
  96. package/dist/processes/classify.d.ts +3 -0
  97. package/dist/processes/classify.js +39 -0
  98. package/dist/processes/extract-from-region.d.ts +19 -0
  99. package/dist/processes/extract-from-region.js +415 -0
  100. package/dist/processes/extract.d.ts +25 -0
  101. package/dist/processes/extract.js +223 -0
  102. package/dist/processes/locate.d.ts +3 -0
  103. package/dist/processes/locate.js +274 -0
  104. package/dist/processes/proposal-location.d.ts +19 -0
  105. package/dist/processes/proposal-location.js +396 -0
  106. package/dist/processes/whole-object.d.ts +7 -0
  107. package/dist/processes/whole-object.js +272 -0
  108. package/dist/segmentation.d.ts +10 -0
  109. package/dist/segmentation.js +4 -0
  110. package/dist/shared/choice-mass.d.ts +2 -0
  111. package/dist/shared/choice-mass.js +19 -0
  112. package/dist/shared/gate.d.ts +11 -0
  113. package/dist/shared/gate.js +52 -0
  114. package/dist/shared/input-budget.d.ts +7 -0
  115. package/dist/shared/input-budget.js +21 -0
  116. package/dist/shared/scope.d.ts +12 -0
  117. package/dist/shared/scope.js +29 -0
  118. package/docs/0.2.0-plan.md +190 -0
  119. package/docs/README.ru.md +258 -0
  120. package/docs/acceptance.md +144 -0
  121. package/docs/api.md +49 -0
  122. package/docs/architecture.md +45 -0
  123. package/docs/benchmarks.md +63 -0
  124. package/docs/categorical-presence-port.md +26 -0
  125. package/docs/evidence/current-linux-check.json +15 -0
  126. package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
  127. package/docs/evidence/decisions-completion-v1-report.json +1205 -0
  128. package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
  129. package/docs/evidence/decisions-selection-v1-report.json +1457 -0
  130. package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
  131. package/docs/evidence/linux-check.json +20 -0
  132. package/docs/evidence/linux-check.txt +205 -0
  133. package/docs/evidence/live-release-smoke-manifest.json +82 -0
  134. package/docs/evidence/live-release-smoke-provenance.json +9 -0
  135. package/docs/evidence/live-release-smoke-report.json +2136 -0
  136. package/docs/evidence/live-v1-manifest.json +84 -0
  137. package/docs/evidence/live-v1-provenance.json +9 -0
  138. package/docs/evidence/live-v1-report.json +15555 -0
  139. package/docs/evidence/luna-decisions-fine-probe.json +19 -0
  140. package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
  141. package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
  142. package/docs/evidence/pack-check.json +26 -0
  143. package/docs/evidence/protocol-probes.json +61 -0
  144. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
  145. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
  146. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
  147. package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
  148. package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
  149. package/docs/evidence/v02/baseline.json +1043 -0
  150. package/docs/evidence/v02/boundary-variants.md +17 -0
  151. package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
  152. package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
  153. package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
  154. package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
  155. package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
  156. package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
  157. package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
  158. package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
  159. package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
  160. package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
  161. package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
  162. package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
  163. package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
  164. package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
  165. package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
  166. package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
  167. package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
  168. package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
  169. package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
  170. package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
  171. package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
  172. package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
  173. package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
  174. package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
  175. package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
  176. package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
  177. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
  178. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
  179. package/docs/evidence/v02/categorical-heldout-v4/README.md +35 -0
  180. package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
  181. package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
  182. package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
  183. package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
  184. package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
  185. package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
  186. package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
  187. package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
  188. package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
  189. package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
  190. package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
  191. package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
  192. package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
  193. package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
  194. package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
  195. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
  196. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
  197. package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
  198. package/docs/evidence/v02/ci-012b935.json +17 -0
  199. package/docs/evidence/v02/ci-0c365cb.json +372 -0
  200. package/docs/evidence/v02/ci-185bde8.json +32 -0
  201. package/docs/evidence/v02/ci-24f849f.json +95 -0
  202. package/docs/evidence/v02/ci-25c19cd.json +375 -0
  203. package/docs/evidence/v02/ci-2922caf.json +375 -0
  204. package/docs/evidence/v02/ci-29357d7.json +29 -0
  205. package/docs/evidence/v02/ci-32aa90d.json +24 -0
  206. package/docs/evidence/v02/ci-35e3064.json +376 -0
  207. package/docs/evidence/v02/ci-4331b1b.json +384 -0
  208. package/docs/evidence/v02/ci-43b7426.json +376 -0
  209. package/docs/evidence/v02/ci-450439c.json +373 -0
  210. package/docs/evidence/v02/ci-46283fe.json +29 -0
  211. package/docs/evidence/v02/ci-4c9c09f.json +34 -0
  212. package/docs/evidence/v02/ci-5579646.json +27 -0
  213. package/docs/evidence/v02/ci-6f9cc15.json +127 -0
  214. package/docs/evidence/v02/ci-7c1051f.json +27 -0
  215. package/docs/evidence/v02/ci-9837888.json +34 -0
  216. package/docs/evidence/v02/ci-987ac32.json +29 -0
  217. package/docs/evidence/v02/ci-9d00fe5.json +25 -0
  218. package/docs/evidence/v02/ci-9f85acc.json +127 -0
  219. package/docs/evidence/v02/ci-be275a6.json +201 -0
  220. package/docs/evidence/v02/ci-c107ddf.json +372 -0
  221. package/docs/evidence/v02/ci-c35f67d.json +127 -0
  222. package/docs/evidence/v02/ci-c5eddd8.json +32 -0
  223. package/docs/evidence/v02/ci-d2042e0.json +372 -0
  224. package/docs/evidence/v02/ci-d684259.json +375 -0
  225. package/docs/evidence/v02/ci-e0c190b.json +127 -0
  226. package/docs/evidence/v02/ci-e551253.json +372 -0
  227. package/docs/evidence/v02/ci-f9d4633.json +29 -0
  228. package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
  229. package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
  230. package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
  231. package/docs/evidence/v02/fresh-development.md +16 -0
  232. package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
  233. package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
  234. package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
  235. package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
  236. package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
  237. package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
  238. package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
  239. package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
  240. package/docs/evidence/v02/fresh-text/README.md +23 -0
  241. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
  242. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
  243. package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
  244. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
  245. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
  246. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
  247. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
  248. package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
  249. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
  250. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
  251. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
  252. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
  253. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
  254. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
  255. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
  256. package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
  257. package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
  258. package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
  259. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
  260. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
  261. package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
  262. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
  263. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
  264. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
  265. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
  266. package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
  267. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
  268. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
  269. package/docs/evidence/v02/fresh-text/report.json +284 -0
  270. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
  271. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
  272. package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
  273. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
  274. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
  275. package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
  276. package/docs/evidence/v02/heldout-input-derivation.md +18 -0
  277. package/docs/evidence/v02/integration-0c365cb.json +90 -0
  278. package/docs/evidence/v02/integration-25c19cd.json +52 -0
  279. package/docs/evidence/v02/integration-35e3064.json +41 -0
  280. package/docs/evidence/v02/integration-4331b1b.json +42 -0
  281. package/docs/evidence/v02/integration-4c9c09f.json +46 -0
  282. package/docs/evidence/v02/integration-8010aad.json +48 -0
  283. package/docs/evidence/v02/integration-c1aa640.json +41 -0
  284. package/docs/evidence/v02/integration-eea1790.json +46 -0
  285. package/docs/evidence/v02/integration-fa82e65.json +42 -0
  286. package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
  287. package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
  288. package/docs/evidence/v02/local-archive-cleanup.json +59 -0
  289. package/docs/evidence/v02/native-base64-after.json +30 -0
  290. package/docs/evidence/v02/native-base64-before.json +32 -0
  291. package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
  292. package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
  293. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
  294. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
  295. package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
  296. package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
  297. package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
  298. package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
  299. package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
  300. package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
  301. package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
  302. package/docs/evidence/v02/native-windows-eea1790.json +228 -0
  303. package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
  304. package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
  305. package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
  306. package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
  307. package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
  308. package/docs/evidence/v02/proposal-resource-soak-261dc23.md +15 -0
  309. package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
  310. package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
  311. package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
  312. package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
  313. package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
  314. package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
  315. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
  316. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
  317. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
  318. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
  319. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
  320. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
  321. package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
  322. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
  323. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
  324. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
  325. package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
  326. package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
  327. package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
  328. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
  329. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
  330. package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
  331. package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
  332. package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
  333. package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
  334. package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
  335. package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
  336. package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
  337. package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
  338. package/docs/evidence/v02/seed-outline/summary.json +487 -0
  339. package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
  340. package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
  341. package/docs/evidence/v02/segmentation/README.md +21 -0
  342. package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
  343. package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
  344. package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
  345. package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
  346. package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
  347. package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
  348. package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
  349. package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
  350. package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
  351. package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
  352. package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
  353. package/docs/evidence/vision-cost-reconciliation.json +37 -0
  354. package/docs/evidence/vision-model-summary.json +562 -0
  355. package/docs/evidence/vision-selection-v1-manifest.json +95 -0
  356. package/docs/evidence/vision-selection-v1-report.json +4598 -0
  357. package/docs/evidence/vision-selection-v2-manifest.json +95 -0
  358. package/docs/evidence/vision-selection-v2-report.json +1139 -0
  359. package/docs/evidence/windows-native.json +73 -0
  360. package/docs/evidence/windows-secrets.json +46 -0
  361. package/docs/operations.md +21 -0
  362. package/docs/plan.md +27 -0
  363. package/docs/provenance.json +32 -0
  364. package/docs/publishing.md +30 -0
  365. package/docs/research/text-transport-audit.md +66 -0
  366. package/docs/results.md +69 -0
  367. package/docs/v02-usage.md +162 -0
  368. package/docs/vision-results.md +78 -0
  369. package/package.json +114 -4
@@ -0,0 +1,19 @@
1
+ import type { LocateInput, LocalizationResult, RasterImage, RasterPort } from '../entities/contracts.js';
2
+ import type { CutoutRasterPort } from '../entities/cutout-raster.js';
3
+ import type { ExtractInput, ExtractionResult, SegmentationPort } from '../entities/segmentation.js';
4
+ import type { SegmentationBoxCheckPort } from '../entities/segmentation-verification.js';
5
+ type LocateRegion = (source: RasterImage, request: Omit<LocateInput, 'image' | 'mode' | 'signal'> & {
6
+ signal: AbortSignal;
7
+ }) => Promise<LocalizationResult>;
8
+ interface ExtractFromRegionDependencies {
9
+ raster: RasterPort;
10
+ image: CutoutRasterPort;
11
+ segmenter: SegmentationPort;
12
+ boxCheck: SegmentationBoxCheckPort;
13
+ locateRegion: LocateRegion;
14
+ }
15
+ /** Original region seed, full-source mask, closed containment check and at most one repair. */
16
+ export declare function extractFromRegion(input: ExtractInput & {
17
+ signal: AbortSignal;
18
+ }, dependencies: ExtractFromRegionDependencies): Promise<ExtractionResult>;
19
+ export {};
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { DecisionPort, LocateInput, LocalizationResult, RasterPort, RefinementPort } from '../entities/contracts.js';
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>;