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,216 @@
1
+ import sharp from 'sharp';
2
+ import { z } from 'zod';
3
+ import { limits, parse, pixelRectSchema } from '../entities/contracts.js';
4
+ import { ClassifierError, assertActive } from '../entities/errors.js';
5
+ function contains(outer, inner) {
6
+ return (inner.left >= outer.left &&
7
+ inner.top >= outer.top &&
8
+ inner.left + inner.width <= outer.left + outer.width &&
9
+ inner.top + inner.height <= outer.top + outer.height);
10
+ }
11
+ async function normalizeImage(bytes, signal) {
12
+ assertActive(signal);
13
+ const pipeline = sharp(bytes, { limitInputPixels: limits.imagePixels, failOn: 'error' });
14
+ try {
15
+ const metadata = await pipeline.metadata();
16
+ assertActive(signal);
17
+ if (!['png', 'jpeg', 'webp'].includes(metadata.format ?? '') || (metadata.pages ?? 1) !== 1)
18
+ throw new ClassifierError('INVALID_INPUT', 'Use a single PNG, JPEG or WebP image');
19
+ const normalized = await pipeline
20
+ .rotate()
21
+ .ensureAlpha()
22
+ .png()
23
+ .toBuffer({ resolveWithObject: true });
24
+ assertActive(signal);
25
+ if (normalized.data.byteLength > limits.imageBytes ||
26
+ normalized.info.width > 16384 ||
27
+ normalized.info.height > 16384)
28
+ throw new ClassifierError('RESOURCE_LIMIT', 'Decoded source exceeds image byte or side limits');
29
+ return { bytes: normalized.data, width: normalized.info.width, height: normalized.info.height };
30
+ }
31
+ catch (error) {
32
+ assertActive(signal);
33
+ if (error instanceof ClassifierError)
34
+ throw error;
35
+ throw new ClassifierError('INVALID_INPUT', 'Image cannot be decoded within pixel limits');
36
+ }
37
+ finally {
38
+ pipeline.destroy();
39
+ }
40
+ }
41
+ async function cropImage(image, region, signal, scale = 1) {
42
+ assertActive(signal);
43
+ if (!contains({ left: 0, top: 0, width: image.width, height: image.height }, region))
44
+ throw new ClassifierError('INVALID_INPUT', 'ROI lies outside oriented source image');
45
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels })
46
+ .extract(region)
47
+ .resize(region.width * scale, region.height * scale, { kernel: 'nearest' });
48
+ try {
49
+ const data = await pipeline.png().toBuffer();
50
+ assertActive(signal);
51
+ return { bytes: data, width: region.width * scale, height: region.height * scale };
52
+ }
53
+ finally {
54
+ pipeline.destroy();
55
+ }
56
+ }
57
+ async function imageState(image, signal) {
58
+ assertActive(signal);
59
+ const scale = Math.min(1568 / Math.max(image.width, image.height), Math.max(1, 280 / Math.min(image.width, image.height)));
60
+ const width = Math.max(1, Math.round(image.width * scale)), height = Math.max(1, Math.round(image.height * scale));
61
+ const outWidth = Math.max(280, width), outHeight = Math.max(280, height);
62
+ const left = Math.floor((outWidth - width) / 2), top = Math.floor((outHeight - height) / 2);
63
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels })
64
+ .resize(width, height, { kernel: 'nearest' })
65
+ .extend({
66
+ left,
67
+ top,
68
+ right: outWidth - width - left,
69
+ bottom: outHeight - height - top,
70
+ background: '#000000',
71
+ });
72
+ try {
73
+ const bytes = await pipeline.png().toBuffer();
74
+ assertActive(signal);
75
+ if (bytes.length > limits.imageBytes)
76
+ throw new ClassifierError('RESOURCE_LIMIT', 'Prepared image is too large');
77
+ const part = {
78
+ type: 'image_url',
79
+ image_url: { url: `data:image/png;base64,${bytes.toString('base64')}`, detail: 'high' },
80
+ };
81
+ return [
82
+ part,
83
+ JSON.stringify({
84
+ sourceImageTransform: {
85
+ source: { width: image.width, height: image.height },
86
+ output: { width: outWidth, height: outHeight },
87
+ content: { left, top, width, height },
88
+ scaleX: width / image.width,
89
+ scaleY: height / image.height,
90
+ },
91
+ }),
92
+ ];
93
+ }
94
+ finally {
95
+ pipeline.destroy();
96
+ }
97
+ }
98
+ async function renderGrid(image, region, cells, labels, signal) {
99
+ const crop = await cropImage(image, region, signal);
100
+ const scale = Math.min(1512 / Math.max(crop.width, crop.height), Math.max(1, 900 / Math.min(crop.width, crop.height)));
101
+ const width = Math.max(1, Math.round(crop.width * scale)), height = Math.max(1, Math.round(crop.height * scale));
102
+ const rectangles = cells
103
+ .map((cell) => {
104
+ const label = labels.get(cell.id);
105
+ if (!label || !/^[PC][0-9]{2,3}$/.test(label))
106
+ throw new ClassifierError('INVALID_INPUT', 'Invalid grid label');
107
+ const x = ((cell.region.left - region.left) * width) / region.width, y = ((cell.region.top - region.top) * height) / region.height;
108
+ const w = (cell.region.width * width) / region.width, h = (cell.region.height * height) / region.height;
109
+ const font = Math.max(3, Math.min(18, w / (label.length * 0.7), h / 2));
110
+ return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="white" stroke-width="1"/><rect x="${x + 1}" y="${y + 1}" width="${Math.min(w - 1, font * label.length * 0.7)}" height="${font + 2}" fill="#111827" fill-opacity="0.78"/><text x="${x + 2}" y="${y + font}" font-family="DejaVu Sans,sans-serif" font-size="${font}" fill="white">${label}</text>`;
111
+ })
112
+ .join('');
113
+ const svg = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${rectangles}</svg>`);
114
+ const pipeline = sharp(crop.bytes, { limitInputPixels: limits.imagePixels })
115
+ .resize(width, height, { kernel: 'nearest' })
116
+ .composite([{ input: svg }]);
117
+ try {
118
+ const bytes = await pipeline.png().toBuffer();
119
+ assertActive(signal);
120
+ return { bytes, width, height };
121
+ }
122
+ finally {
123
+ pipeline.destroy();
124
+ }
125
+ }
126
+ export function createSharpRaster() {
127
+ return { normalize: normalizeImage, crop: cropImage, imageState, grid: renderGrid };
128
+ }
129
+ /** Decode an oriented raster into the canonical RGBA8 pixel layout used by adapters. */
130
+ export async function readCanonicalPixels(image, signal) {
131
+ assertActive(signal);
132
+ if (!Number.isSafeInteger(image.width) ||
133
+ !Number.isSafeInteger(image.height) ||
134
+ image.width < 1 ||
135
+ image.height < 1 ||
136
+ image.width > 16384 ||
137
+ image.height > 16384 ||
138
+ image.width * image.height > limits.imagePixels)
139
+ throw new ClassifierError('RESOURCE_LIMIT', 'Raster dimensions exceed pixel limits');
140
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels, failOn: 'error' })
141
+ .rotate()
142
+ .ensureAlpha()
143
+ .raw();
144
+ try {
145
+ const result = await pipeline.toBuffer({ resolveWithObject: true });
146
+ assertActive(signal);
147
+ if (result.info.width !== image.width ||
148
+ result.info.height !== image.height ||
149
+ result.info.channels !== 4 ||
150
+ result.data.byteLength !== image.width * image.height * 4)
151
+ throw new ClassifierError('INVALID_INPUT', 'Raster dimensions do not match decoded pixels');
152
+ return { data: result.data, width: result.info.width, height: result.info.height };
153
+ }
154
+ catch (error) {
155
+ assertActive(signal);
156
+ if (error instanceof ClassifierError)
157
+ throw error;
158
+ throw new ClassifierError('INVALID_INPUT', 'Raster cannot be decoded as canonical RGBA8');
159
+ }
160
+ finally {
161
+ pipeline.destroy();
162
+ }
163
+ }
164
+ /** Vision adapter that retains the existing sharp imageState layout for every supplied view. */
165
+ export function createVisionRepresentation(raster) {
166
+ return {
167
+ async prepare(input) {
168
+ assertActive(input.signal);
169
+ if (input.views.length > 9)
170
+ throw new ClassifierError('RESOURCE_LIMIT', 'A representation request may contain at most nine views', {
171
+ component: 'representation',
172
+ });
173
+ const state = [];
174
+ const metadata = { profile: 'vision', bytes: 0, views: [] };
175
+ for (const view of input.views) {
176
+ assertActive(input.signal);
177
+ const parts = await raster.imageState(view.image, input.signal);
178
+ state.push(...parts);
179
+ const imagePart = parts.find((part) => typeof part !== 'string');
180
+ const transformPart = parts.find((part) => typeof part === 'string');
181
+ if (imagePart?.type !== 'image_url' || !transformPart)
182
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster did not produce an image and transform');
183
+ const payload = imagePart.image_url.url.slice('data:image/png;base64,'.length);
184
+ metadata.bytes += Buffer.from(payload, 'base64').byteLength;
185
+ let json;
186
+ try {
187
+ json = JSON.parse(transformPart);
188
+ }
189
+ catch {
190
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster returned invalid transform JSON');
191
+ }
192
+ const transform = parse(z.object({
193
+ sourceImageTransform: z.object({
194
+ output: z.object({
195
+ width: z.number().int().positive().max(16384),
196
+ height: z.number().int().positive().max(16384),
197
+ }),
198
+ content: pixelRectSchema,
199
+ }),
200
+ }), json, 'INVALID_RESPONSE');
201
+ if (!contains({ left: 0, top: 0, ...transform.sourceImageTransform.output }, transform.sourceImageTransform.content))
202
+ throw new ClassifierError('INVALID_RESPONSE', 'Vision raster content lies outside output');
203
+ metadata.views.push({
204
+ id: view.id,
205
+ sourceRegion: view.sourceRegion,
206
+ width: transform.sourceImageTransform.output.width,
207
+ height: transform.sourceImageTransform.output.height,
208
+ scaleX: transform.sourceImageTransform.content.width / view.sourceRegion.width,
209
+ scaleY: transform.sourceImageTransform.content.height / view.sourceRegion.height,
210
+ content: transform.sourceImageTransform.content,
211
+ });
212
+ }
213
+ return { state, metadata };
214
+ },
215
+ };
216
+ }
@@ -0,0 +1,2 @@
1
+ import type { SegmentationBoxPresentationPort } from '../../entities/segmentation-verification.js';
2
+ export declare function createSharpSegmentationBoxPresentation(): SegmentationBoxPresentationPort;
@@ -0,0 +1,136 @@
1
+ import sharp from 'sharp';
2
+ import { z } from 'zod';
3
+ import { limits, pixelRectSchema } from '../../entities/contracts.js';
4
+ import { ClassifierError, assertActive } from '../../entities/errors.js';
5
+ const rasterSchema = z
6
+ .object({
7
+ data: z.instanceof(Uint8Array),
8
+ width: z.number().int().positive().max(16384),
9
+ height: z.number().int().positive().max(16384),
10
+ format: z.literal('rgba8'),
11
+ })
12
+ .strict();
13
+ const inputSchema = z
14
+ .object({
15
+ image: rasterSchema,
16
+ region: pixelRectSchema,
17
+ seedRegion: pixelRectSchema,
18
+ roi: pixelRectSchema,
19
+ target: z.string().trim().min(1).max(2000),
20
+ exclusions: z.array(z.string().trim().min(1).max(500)).max(16),
21
+ signal: z.custom((value) => value instanceof AbortSignal),
22
+ })
23
+ .strict();
24
+ function inside(rect, width, height) {
25
+ return rect.left + rect.width <= width && rect.top + rect.height <= height;
26
+ }
27
+ function overlaps(first, second) {
28
+ return (first.left < second.left + second.width &&
29
+ second.left < first.left + first.width &&
30
+ first.top < second.top + second.height &&
31
+ second.top < first.top + first.height);
32
+ }
33
+ function cloneRect(rect) {
34
+ return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
35
+ }
36
+ function snapshot(value) {
37
+ const parsed = inputSchema.safeParse(value);
38
+ if (!parsed.success)
39
+ throw new ClassifierError('INVALID_INPUT', 'Invalid segmentation box presentation input', {
40
+ paths: parsed.error.issues.map((issue) => issue.path.join('.')),
41
+ });
42
+ const input = parsed.data;
43
+ const pixels = input.image.width * input.image.height;
44
+ if (!Number.isSafeInteger(pixels) ||
45
+ pixels < 1 ||
46
+ pixels > limits.imagePixels ||
47
+ input.image.data.byteLength !== pixels * 4 ||
48
+ !inside(input.region, input.image.width, input.image.height) ||
49
+ !inside(input.seedRegion, input.image.width, input.image.height) ||
50
+ !inside(input.roi, input.image.width, input.image.height) ||
51
+ input.region.left < input.roi.left ||
52
+ input.region.top < input.roi.top ||
53
+ input.region.left + input.region.width > input.roi.left + input.roi.width ||
54
+ input.region.top + input.region.height > input.roi.top + input.roi.height ||
55
+ !overlaps(input.seedRegion, input.region) ||
56
+ !overlaps(input.seedRegion, input.roi))
57
+ throw new ClassifierError('INVALID_INPUT', 'Box, seed, image, and hard ROI geometry disagree');
58
+ const image = {
59
+ data: Uint8Array.from(input.image.data),
60
+ width: input.image.width,
61
+ height: input.image.height,
62
+ format: 'rgba8',
63
+ };
64
+ return {
65
+ image,
66
+ region: cloneRect(input.region),
67
+ seedRegion: cloneRect(input.seedRegion),
68
+ roi: cloneRect(input.roi),
69
+ target: input.target,
70
+ exclusions: [...input.exclusions],
71
+ signal: input.signal,
72
+ };
73
+ }
74
+ function overlaySvg(input) {
75
+ const { image, region, seedRegion, roi } = input;
76
+ const boxStroke = Math.min(3, region.width, region.height);
77
+ const x = region.left + boxStroke / 2;
78
+ const y = region.top + boxStroke / 2;
79
+ const w = Math.max(0, region.width - boxStroke);
80
+ const h = Math.max(0, region.height - boxStroke);
81
+ const roiStroke = Math.min(2, roi.width, roi.height);
82
+ const roiX = roi.left + roiStroke / 2;
83
+ const roiY = roi.top + roiStroke / 2;
84
+ const roiW = Math.max(0, roi.width - roiStroke);
85
+ const roiH = Math.max(0, roi.height - roiStroke);
86
+ // Use the same pixel-center convention as the native seed point: (size - 1) / 2.
87
+ const cx = seedRegion.left + (seedRegion.width - 1) / 2;
88
+ const cy = seedRegion.top + (seedRegion.height - 1) / 2;
89
+ const seedSize = Math.min(seedRegion.width, seedRegion.height);
90
+ const crossArm = Math.min(5, Math.max(0.5, (seedSize - 1) / 2));
91
+ const crossStroke = Math.min(2, Math.max(1, seedSize));
92
+ return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${image.width}" height="${image.height}"><rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="#ff00ff" stroke-width="${boxStroke}"/><rect x="${roiX}" y="${roiY}" width="${roiW}" height="${roiH}" fill="none" stroke="#ffbf00" stroke-width="${roiStroke}" stroke-dasharray="7 5"/><path d="M ${cx - crossArm} ${cy} H ${cx + crossArm} M ${cx} ${cy - crossArm} V ${cy + crossArm}" fill="none" stroke="#00ffff" stroke-width="${crossStroke}"/></svg>`);
93
+ }
94
+ export function createSharpSegmentationBoxPresentation() {
95
+ return {
96
+ async render(value) {
97
+ const input = snapshot(value);
98
+ const owned = input.image.data;
99
+ let pipeline;
100
+ const abort = () => pipeline?.destroy();
101
+ let listenerAdded = false;
102
+ try {
103
+ assertActive(input.signal);
104
+ pipeline = sharp(owned, {
105
+ raw: { width: input.image.width, height: input.image.height, channels: 4 },
106
+ limitInputPixels: limits.imagePixels,
107
+ failOn: 'error',
108
+ })
109
+ .composite([{ input: overlaySvg(input) }])
110
+ .png({ compressionLevel: 6 });
111
+ input.signal.addEventListener('abort', abort, { once: true });
112
+ listenerAdded = true;
113
+ const { data, info } = await pipeline.toBuffer({ resolveWithObject: true });
114
+ assertActive(input.signal);
115
+ if (info.width !== input.image.width ||
116
+ info.height !== input.image.height ||
117
+ data.byteLength < 1 ||
118
+ data.byteLength > limits.imageBytes)
119
+ throw new ClassifierError('RESOURCE_LIMIT', 'Rendered box presentation exceeds image limits');
120
+ return { bytes: Uint8Array.from(data), width: info.width, height: info.height };
121
+ }
122
+ catch (error) {
123
+ assertActive(input.signal);
124
+ if (error instanceof ClassifierError)
125
+ throw error;
126
+ throw new ClassifierError('PROVIDER', 'Box presentation renderer failed');
127
+ }
128
+ finally {
129
+ if (listenerAdded)
130
+ input.signal.removeEventListener('abort', abort);
131
+ pipeline?.destroy();
132
+ owned.fill(0);
133
+ }
134
+ },
135
+ };
136
+ }
@@ -0,0 +1,3 @@
1
+ import type { CutoutRasterPort } from '../../entities/cutout-raster.js';
2
+ /** Sharp-backed decoder/encoder isolated behind the extraction raster port. */
3
+ export declare function createSharpCutoutRaster(): CutoutRasterPort;
@@ -0,0 +1,79 @@
1
+ import sharp from 'sharp';
2
+ import { ClassifierError, assertActive } from '../../entities/errors.js';
3
+ const maxPixels = 16_777_216;
4
+ /** Sharp-backed decoder/encoder isolated behind the extraction raster port. */
5
+ export function createSharpCutoutRaster() {
6
+ return {
7
+ async rgba(image, signal) {
8
+ assertActive(signal);
9
+ validateRasterImage(image);
10
+ const pipeline = sharp(image.bytes, { limitInputPixels: maxPixels, failOn: 'error' });
11
+ try {
12
+ const output = await pipeline
13
+ .toColourspace('srgb')
14
+ .ensureAlpha()
15
+ .raw()
16
+ .toBuffer({ resolveWithObject: true });
17
+ assertActive(signal);
18
+ if (output.info.width !== image.width ||
19
+ output.info.height !== image.height ||
20
+ output.info.channels !== 4 ||
21
+ output.data.length !== image.width * image.height * 4)
22
+ throw new ClassifierError('INVALID_RESPONSE', 'Cutout raster decoder returned invalid canonical RGBA pixels');
23
+ return {
24
+ data: new Uint8Array(output.data),
25
+ width: image.width,
26
+ height: image.height,
27
+ format: 'rgba8',
28
+ };
29
+ }
30
+ catch (error) {
31
+ if (error instanceof ClassifierError)
32
+ throw error;
33
+ throw new ClassifierError('INVALID_RESPONSE', 'Cutout raster decoder failed');
34
+ }
35
+ finally {
36
+ pipeline.destroy();
37
+ }
38
+ },
39
+ async png(image, signal) {
40
+ assertActive(signal);
41
+ validateRgbaRaster(image);
42
+ const pipeline = sharp(image.data, {
43
+ raw: { width: image.width, height: image.height, channels: 4 },
44
+ });
45
+ try {
46
+ const png = await pipeline.png().toBuffer();
47
+ assertActive(signal);
48
+ return new Uint8Array(png);
49
+ }
50
+ catch (error) {
51
+ if (error instanceof ClassifierError)
52
+ throw error;
53
+ throw new ClassifierError('PROVIDER', 'Cutout PNG encoder failed');
54
+ }
55
+ finally {
56
+ pipeline.destroy();
57
+ }
58
+ },
59
+ };
60
+ }
61
+ function validateRasterImage(image) {
62
+ if (!Number.isSafeInteger(image.width) ||
63
+ !Number.isSafeInteger(image.height) ||
64
+ image.width < 1 ||
65
+ image.height < 1 ||
66
+ image.width * image.height > maxPixels ||
67
+ image.bytes.byteLength < 1)
68
+ throw new ClassifierError('INVALID_RESPONSE', 'Raster normalizer returned invalid source data');
69
+ }
70
+ function validateRgbaRaster(image) {
71
+ if (image.format !== 'rgba8' ||
72
+ !Number.isSafeInteger(image.width) ||
73
+ !Number.isSafeInteger(image.height) ||
74
+ image.width < 1 ||
75
+ image.height < 1 ||
76
+ image.width * image.height > maxPixels ||
77
+ image.data.length !== image.width * image.height * 4)
78
+ throw new ClassifierError('INVALID_RESPONSE', 'Extraction returned invalid canonical RGBA pixels');
79
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import type { SegmentationPort } from '../../../entities/segmentation.js';
3
+ declare const optionsSchema: z.ZodObject<{
4
+ assetPath: z.ZodString;
5
+ }, "strict", z.ZodTypeAny, {
6
+ assetPath: string;
7
+ }, {
8
+ assetPath: string;
9
+ }>;
10
+ export type GrabCutSegmenterOptions = z.input<typeof optionsSchema>;
11
+ /** Explicit pinned OpenCV GrabCut adapter. The supplied native asset is never downloaded. */
12
+ export declare function createGrabCutSegmenter(options: GrabCutSegmenterOptions): Promise<SegmentationPort>;
13
+ export {};
@@ -0,0 +1,118 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { ClassifierError, assertActive } from '../../../entities/errors.js';
3
+ import { z } from 'zod';
4
+ import { limits, pixelRectSchema } from '../../../entities/contracts.js';
5
+ import { NativeChildPool } from '../native-child.js';
6
+ import { nativeErrorReplySchema, nativeSegmentationReplySchema } from '../native-protocol.js';
7
+ import { verifyPinnedAsset } from '../verify-asset.js';
8
+ import { probeBackend } from '../probe-backend.js';
9
+ const optionsSchema = z.object({ assetPath: z.string().min(1).max(4096) }).strict();
10
+ /** Explicit pinned OpenCV GrabCut adapter. The supplied native asset is never downloaded. */
11
+ export async function createGrabCutSegmenter(options) {
12
+ const checked = optionsSchema.safeParse(options);
13
+ if (!checked.success)
14
+ throw new ClassifierError('INVALID_INPUT', 'Invalid GrabCut options');
15
+ const parsed = checked.data;
16
+ await verifyPinnedAsset(parsed.assetPath, {
17
+ bytes: 10_964_323,
18
+ sha256: '63366510248adf3a7eddf3e793dd825404efb7df3749f4d6f8557c7fa4ca8aa0',
19
+ }, 'OpenCV.js');
20
+ const workerUrl = new URL(import.meta.url.endsWith('.ts') ? '../native-worker.ts' : '../native-worker.js', import.meta.url);
21
+ await probeBackend(workerUrl, 'grabcut', { assetPath: parsed.assetPath });
22
+ const pool = new NativeChildPool({
23
+ workerUrl,
24
+ maxActive: 1,
25
+ maxWaiting: 2,
26
+ jobTimeoutMs: 10_000,
27
+ abortGraceMs: 250,
28
+ encode: (job) => Buffer.from(JSON.stringify({ id: job.id, ...job.payload })),
29
+ decode: (raw, id) => {
30
+ const error = nativeErrorReplySchema.safeParse(raw);
31
+ if (error.success) {
32
+ if (error.data.id !== id)
33
+ throw new ClassifierError('INVALID_RESPONSE', 'Native child error ID mismatch');
34
+ throw new ClassifierError(error.data.code, error.data.message);
35
+ }
36
+ const reply = nativeSegmentationReplySchema.safeParse(raw);
37
+ if (!reply.success || reply.data.id !== id)
38
+ throw new ClassifierError('INVALID_RESPONSE', 'Native child returned an invalid or mismatched reply');
39
+ return reply.data;
40
+ },
41
+ });
42
+ return {
43
+ async segment(input) {
44
+ const started = performance.now();
45
+ assertActive(input.signal);
46
+ let image;
47
+ const raw = await pool.runLazy(input.signal, () => {
48
+ image = snapshotInput(input);
49
+ return {
50
+ kind: 'grabcut',
51
+ width: image.width,
52
+ height: image.height,
53
+ rgba: Buffer.from(image.data).toString('base64'),
54
+ seedRegion: image.seedRegion,
55
+ wholeRegion: image.wholeRegion,
56
+ roi: image.roi,
57
+ prompt: image.prompt,
58
+ assetPath: parsed.assetPath,
59
+ };
60
+ });
61
+ const reply = nativeSegmentationReplySchema.parse(raw);
62
+ const mask = Buffer.from(reply.mask, 'base64');
63
+ if (reply.width !== image.width ||
64
+ reply.height !== image.height ||
65
+ mask.length !== image.width * image.height ||
66
+ mask.toString('base64') !== reply.mask)
67
+ throw new ClassifierError('INVALID_RESPONSE', 'Native child mask dimensions or encoding are invalid');
68
+ return {
69
+ status: 'segmented',
70
+ mask: {
71
+ data: new Uint8Array(mask),
72
+ width: image.width,
73
+ height: image.height,
74
+ format: 'binary-u8',
75
+ },
76
+ metadata: {
77
+ method: reply.method,
78
+ backend: 'grabcut',
79
+ durationMs: Math.max(0, performance.now() - started),
80
+ input: { width: image.width, height: image.height },
81
+ },
82
+ };
83
+ },
84
+ settle: () => pool.settle(),
85
+ dispose: () => pool.dispose(),
86
+ };
87
+ }
88
+ function snapshotInput(input) {
89
+ const image = input.image;
90
+ const pixels = image.width * image.height;
91
+ const prompt = input.prompt ?? 'whole-region';
92
+ if (image.format !== 'rgba8' ||
93
+ !Number.isSafeInteger(image.width) ||
94
+ !Number.isSafeInteger(image.height) ||
95
+ !Number.isSafeInteger(pixels) ||
96
+ image.width < 1 ||
97
+ image.height < 1 ||
98
+ pixels > limits.imagePixels ||
99
+ image.data.length !== pixels * 4)
100
+ throw new ClassifierError('INVALID_INPUT', 'Segmentation requires canonical RGBA8 within the image pixel limit');
101
+ if ((prompt !== 'whole-region' && prompt !== 'seed-region') ||
102
+ !pixelRectSchema.safeParse(input.seedRegion).success ||
103
+ !pixelRectSchema.safeParse(input.wholeRegion).success ||
104
+ !pixelRectSchema.safeParse(input.roi).success)
105
+ throw new ClassifierError('INVALID_INPUT', 'Segmentation regions must be positive integer rectangles');
106
+ for (const region of [input.seedRegion, input.wholeRegion, input.roi])
107
+ if (region.left + region.width > image.width || region.top + region.height > image.height)
108
+ throw new ClassifierError('INVALID_INPUT', 'Segmentation region lies outside the source image');
109
+ return {
110
+ data: new Uint8Array(image.data),
111
+ width: image.width,
112
+ height: image.height,
113
+ seedRegion: { ...input.seedRegion },
114
+ wholeRegion: { ...input.wholeRegion },
115
+ roi: { ...input.roi },
116
+ prompt,
117
+ };
118
+ }
@@ -0,0 +1,47 @@
1
+ import type { NativeSegmentationReply, NativeSegmentationRequest } from '../native-protocol.js';
2
+ export declare function probeGrabCut(assetPath: string): Promise<void>;
3
+ export declare function initializeGrabCutMask(input: {
4
+ flags: Uint8Array;
5
+ width: number;
6
+ height: number;
7
+ seedRegion: {
8
+ left: number;
9
+ top: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ wholeRegion: {
14
+ left: number;
15
+ top: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ roi: {
20
+ left: number;
21
+ top: number;
22
+ width: number;
23
+ height: number;
24
+ };
25
+ prompt: 'whole-region' | 'seed-region';
26
+ labels: {
27
+ background: number;
28
+ foreground: number;
29
+ probableBackground: number;
30
+ probableForeground: number;
31
+ };
32
+ }): void;
33
+ /** Run native GrabCut, then reacquire Mat.data before copying its post-call flags. */
34
+ export declare function runGrabCutAndCopyMask(mat: {
35
+ data?: unknown;
36
+ }, width: number, height: number, roi: {
37
+ left: number;
38
+ top: number;
39
+ width: number;
40
+ height: number;
41
+ }, runNative: () => void, labels: {
42
+ foreground: number;
43
+ probableForeground: number;
44
+ }): Uint8Array;
45
+ export declare function runGrabCutTask(request: Extract<NativeSegmentationRequest, {
46
+ kind: 'grabcut';
47
+ }>): Promise<NativeSegmentationReply>;