zorya-classifier 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/NOTICE.md +2 -0
  2. package/README.md +37 -9
  3. package/dist/app/cli-arguments.d.ts +40 -0
  4. package/dist/app/cli-arguments.js +67 -0
  5. package/dist/app/create-classifier.d.ts +24 -0
  6. package/dist/app/create-classifier.js +226 -14
  7. package/dist/app/extraction-files.d.ts +9 -0
  8. package/dist/app/extraction-files.js +28 -0
  9. package/dist/cli.js +58 -14
  10. package/dist/entities/boundary-view.d.ts +29 -0
  11. package/dist/entities/boundary-view.js +1 -0
  12. package/dist/entities/contracts.d.ts +81 -1
  13. package/dist/entities/contracts.js +7 -0
  14. package/dist/entities/cutout-raster.d.ts +7 -0
  15. package/dist/entities/cutout-raster.js +1 -0
  16. package/dist/entities/object-proposals.d.ts +56 -0
  17. package/dist/entities/object-proposals.js +8 -0
  18. package/dist/entities/representation.d.ts +225 -0
  19. package/dist/entities/representation.js +33 -0
  20. package/dist/entities/segmentation-metadata.d.ts +11 -0
  21. package/dist/entities/segmentation-metadata.js +1 -0
  22. package/dist/entities/segmentation-verification.d.ts +31 -0
  23. package/dist/entities/segmentation-verification.js +1 -0
  24. package/dist/entities/segmentation.d.ts +164 -0
  25. package/dist/entities/segmentation.js +5 -0
  26. package/dist/entities/whole-object.d.ts +132 -0
  27. package/dist/entities/whole-object.js +7 -0
  28. package/dist/features/choice.d.ts +1 -1
  29. package/dist/features/choice.js +2 -2
  30. package/dist/features/segmentation/box-check.d.ts +10 -0
  31. package/dist/features/segmentation/box-check.js +299 -0
  32. package/dist/features/segmentation/mask.d.ts +6 -0
  33. package/dist/features/segmentation/mask.js +89 -0
  34. package/dist/features/segmentation/packed-mask.d.ts +5 -0
  35. package/dist/features/segmentation/packed-mask.js +53 -0
  36. package/dist/index.d.ts +6 -1
  37. package/dist/infrastructure/boundary-view.d.ts +2 -0
  38. package/dist/infrastructure/boundary-view.js +263 -0
  39. package/dist/infrastructure/openrouter-common.d.ts +8 -8
  40. package/dist/infrastructure/openrouter-vision.d.ts +3 -0
  41. package/dist/infrastructure/openrouter-vision.js +5 -0
  42. package/dist/infrastructure/raster.d.ts +10 -1
  43. package/dist/infrastructure/raster.js +99 -4
  44. package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
  45. package/dist/infrastructure/segmentation/box-presentation.js +136 -0
  46. package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
  47. package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
  48. package/dist/infrastructure/segmentation/grabcut/index.d.ts +17 -0
  49. package/dist/infrastructure/segmentation/grabcut/index.js +121 -0
  50. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
  51. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
  52. package/dist/infrastructure/segmentation/index.d.ts +2 -0
  53. package/dist/infrastructure/segmentation/index.js +2 -0
  54. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +66 -0
  55. package/dist/infrastructure/segmentation/mobile-sam/index.js +478 -0
  56. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
  57. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
  58. package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
  59. package/dist/infrastructure/segmentation/native-child.js +297 -0
  60. package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
  61. package/dist/infrastructure/segmentation/native-protocol.js +242 -0
  62. package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
  63. package/dist/infrastructure/segmentation/native-worker.js +93 -0
  64. package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
  65. package/dist/infrastructure/segmentation/probe-backend.js +31 -0
  66. package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
  67. package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
  68. package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
  69. package/dist/infrastructure/segmentation/verify-asset.js +32 -0
  70. package/dist/infrastructure/text-representation.d.ts +23 -0
  71. package/dist/infrastructure/text-representation.js +226 -0
  72. package/dist/infrastructure/vision-chat.d.ts +1 -1
  73. package/dist/infrastructure/vision-chat.js +13 -4
  74. package/dist/openai-compatible.d.ts +3 -0
  75. package/dist/openai-compatible.js +4 -0
  76. package/dist/openrouter.d.ts +2 -2
  77. package/dist/openrouter.js +1 -1
  78. package/dist/processes/boundary-state.d.ts +12 -0
  79. package/dist/processes/boundary-state.js +196 -0
  80. package/dist/processes/classify.d.ts +2 -1
  81. package/dist/processes/classify.js +21 -3
  82. package/dist/processes/extract-from-region.d.ts +19 -0
  83. package/dist/processes/extract-from-region.js +415 -0
  84. package/dist/processes/extract.d.ts +25 -0
  85. package/dist/processes/extract.js +223 -0
  86. package/dist/processes/locate.d.ts +2 -1
  87. package/dist/processes/locate.js +84 -11
  88. package/dist/processes/proposal-location.d.ts +19 -0
  89. package/dist/processes/proposal-location.js +396 -0
  90. package/dist/processes/whole-object.d.ts +7 -0
  91. package/dist/processes/whole-object.js +272 -0
  92. package/dist/segmentation.d.ts +10 -0
  93. package/dist/segmentation.js +4 -0
  94. package/dist/shared/input-budget.d.ts +7 -0
  95. package/dist/shared/input-budget.js +21 -0
  96. package/docs/0.2.0-plan.md +190 -0
  97. package/docs/README.ru.md +43 -14
  98. package/docs/acceptance.md +116 -3
  99. package/docs/api.md +12 -6
  100. package/docs/architecture.md +22 -7
  101. package/docs/benchmarks.md +10 -0
  102. package/docs/categorical-presence-port.md +26 -0
  103. package/docs/evidence/v02/baseline.json +1043 -0
  104. package/docs/evidence/v02/boundary-variants.md +17 -0
  105. package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
  106. package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
  107. package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
  108. package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
  109. package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
  110. package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
  111. package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
  112. package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
  113. package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
  114. package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
  115. package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
  116. package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
  117. package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
  118. package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
  119. package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
  120. package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
  121. package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
  122. package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
  123. package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
  124. package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
  125. package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
  126. package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
  127. package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
  128. package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
  129. package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
  130. package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
  131. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
  132. package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
  133. package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
  134. package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
  135. package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
  136. package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
  137. package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
  138. package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
  139. package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
  140. package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
  141. package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
  142. package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
  143. package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
  144. package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
  145. package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
  146. package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
  147. package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
  148. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
  149. package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
  150. package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
  151. package/docs/evidence/v02/ci-012b935.json +17 -0
  152. package/docs/evidence/v02/ci-0c365cb.json +372 -0
  153. package/docs/evidence/v02/ci-185bde8.json +32 -0
  154. package/docs/evidence/v02/ci-24f849f.json +95 -0
  155. package/docs/evidence/v02/ci-25c19cd.json +375 -0
  156. package/docs/evidence/v02/ci-2922caf.json +375 -0
  157. package/docs/evidence/v02/ci-29357d7.json +29 -0
  158. package/docs/evidence/v02/ci-32aa90d.json +24 -0
  159. package/docs/evidence/v02/ci-35e3064.json +376 -0
  160. package/docs/evidence/v02/ci-4331b1b.json +384 -0
  161. package/docs/evidence/v02/ci-43b7426.json +376 -0
  162. package/docs/evidence/v02/ci-450439c.json +373 -0
  163. package/docs/evidence/v02/ci-46283fe.json +29 -0
  164. package/docs/evidence/v02/ci-4c9c09f.json +34 -0
  165. package/docs/evidence/v02/ci-5579646.json +27 -0
  166. package/docs/evidence/v02/ci-6f9cc15.json +127 -0
  167. package/docs/evidence/v02/ci-7c1051f.json +27 -0
  168. package/docs/evidence/v02/ci-9837888.json +34 -0
  169. package/docs/evidence/v02/ci-987ac32.json +29 -0
  170. package/docs/evidence/v02/ci-9d00fe5.json +25 -0
  171. package/docs/evidence/v02/ci-9f85acc.json +127 -0
  172. package/docs/evidence/v02/ci-be275a6.json +201 -0
  173. package/docs/evidence/v02/ci-c107ddf.json +372 -0
  174. package/docs/evidence/v02/ci-c35f67d.json +127 -0
  175. package/docs/evidence/v02/ci-c5eddd8.json +32 -0
  176. package/docs/evidence/v02/ci-d2042e0.json +372 -0
  177. package/docs/evidence/v02/ci-d684259.json +375 -0
  178. package/docs/evidence/v02/ci-e0c190b.json +127 -0
  179. package/docs/evidence/v02/ci-e551253.json +372 -0
  180. package/docs/evidence/v02/ci-f9d4633.json +29 -0
  181. package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
  182. package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
  183. package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
  184. package/docs/evidence/v02/fresh-development.md +16 -0
  185. package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
  186. package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
  187. package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
  188. package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
  189. package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
  190. package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
  191. package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
  192. package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
  193. package/docs/evidence/v02/fresh-text/README.md +23 -0
  194. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
  195. package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
  196. package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
  197. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
  198. package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
  199. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
  200. package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
  201. package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
  202. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
  203. package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
  204. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
  205. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
  206. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
  207. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
  208. package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
  209. package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
  210. package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
  211. package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
  212. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
  213. package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
  214. package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
  215. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
  216. package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
  217. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
  218. package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
  219. package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
  220. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
  221. package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
  222. package/docs/evidence/v02/fresh-text/report.json +284 -0
  223. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
  224. package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
  225. package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
  226. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
  227. package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
  228. package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
  229. package/docs/evidence/v02/heldout-input-derivation.md +18 -0
  230. package/docs/evidence/v02/integration-0c365cb.json +90 -0
  231. package/docs/evidence/v02/integration-25c19cd.json +52 -0
  232. package/docs/evidence/v02/integration-35e3064.json +41 -0
  233. package/docs/evidence/v02/integration-4331b1b.json +42 -0
  234. package/docs/evidence/v02/integration-4c9c09f.json +46 -0
  235. package/docs/evidence/v02/integration-8010aad.json +48 -0
  236. package/docs/evidence/v02/integration-c1aa640.json +41 -0
  237. package/docs/evidence/v02/integration-eea1790.json +46 -0
  238. package/docs/evidence/v02/integration-fa82e65.json +42 -0
  239. package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
  240. package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
  241. package/docs/evidence/v02/local-archive-cleanup.json +59 -0
  242. package/docs/evidence/v02/native-base64-after.json +30 -0
  243. package/docs/evidence/v02/native-base64-before.json +32 -0
  244. package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
  245. package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
  246. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
  247. package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
  248. package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
  249. package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
  250. package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
  251. package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
  252. package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
  253. package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
  254. package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
  255. package/docs/evidence/v02/native-windows-eea1790.json +228 -0
  256. package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
  257. package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
  258. package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
  259. package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
  260. package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
  261. package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
  262. package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
  263. package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
  264. package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
  265. package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
  266. package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
  267. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
  268. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
  269. package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
  270. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
  271. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
  272. package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
  273. package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
  274. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
  275. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
  276. package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
  277. package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
  278. package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
  279. package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
  280. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
  281. package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
  282. package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
  283. package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
  284. package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
  285. package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
  286. package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
  287. package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
  288. package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
  289. package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
  290. package/docs/evidence/v02/seed-outline/summary.json +487 -0
  291. package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
  292. package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
  293. package/docs/evidence/v02/segmentation/README.md +21 -0
  294. package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
  295. package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
  296. package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
  297. package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
  298. package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
  299. package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
  300. package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
  301. package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
  302. package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
  303. package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
  304. package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
  305. package/docs/operations.md +8 -2
  306. package/docs/plan.md +2 -0
  307. package/docs/publishing.md +16 -1
  308. package/docs/research/text-transport-audit.md +66 -0
  309. package/docs/v02-usage.md +164 -0
  310. package/docs/vision-results.md +31 -2
  311. package/package.json +30 -5
@@ -0,0 +1,371 @@
1
+ import sharp from 'sharp';
2
+ import { ClassifierError, assertActive } from '../../entities/errors.js';
3
+ import { objectProposalLimits } from '../../entities/object-proposals.js';
4
+ import { unpackBinaryMask } from '../../features/segmentation/packed-mask.js';
5
+ const columns = 4;
6
+ const rows = 3;
7
+ const tileWidth = 300;
8
+ const tileHeight = 240;
9
+ const sheetWidth = columns * tileWidth;
10
+ const sheetHeight = rows * tileHeight;
11
+ const previewWidth = 142;
12
+ const previewHeight = 200;
13
+ const previewTop = 28;
14
+ const previewLeft = 4;
15
+ /** Generic, bounded rendering for already-validated object mask aliases. */
16
+ export function createSharpObjectProposalPresentation() {
17
+ return {
18
+ async render(input) {
19
+ const signal = input.signal;
20
+ assertSignal(signal);
21
+ const callerImage = input.image;
22
+ assertImage(callerImage);
23
+ const callerSeedRegion = input.seedRegion;
24
+ assertRect(callerSeedRegion, callerImage.width, callerImage.height, 'seedRegion');
25
+ const image = {
26
+ data: new Uint8Array(callerImage.data),
27
+ width: callerImage.width,
28
+ height: callerImage.height,
29
+ format: callerImage.format,
30
+ };
31
+ const seedRegion = { ...callerSeedRegion };
32
+ const proposals = input.proposals;
33
+ if (!Array.isArray(proposals) ||
34
+ proposals.length < 1 ||
35
+ proposals.length > objectProposalLimits.candidates)
36
+ throw invalid('Expected 1..36 object proposals');
37
+ // Own every buffer synchronously, before the first Sharp await.
38
+ const source = image.data;
39
+ let previousAlias = -1;
40
+ for (const proposal of proposals) {
41
+ const match = typeof proposal.id === 'string' ? /^C(\d{2})$/.exec(proposal.id) : null;
42
+ const alias = match ? Number(match[1]) : -1;
43
+ if (alias < 0 || alias >= objectProposalLimits.candidates || alias <= previousAlias)
44
+ throw invalid('Proposal aliases must be unique and retain source order');
45
+ previousAlias = alias;
46
+ }
47
+ const snapshots = proposals.map((proposal) => snapshotProposal(proposal, image));
48
+ if (snapshots.reduce((sum, item) => sum + item.data.byteLength, 0) >
49
+ objectProposalLimits.packedBytes)
50
+ throw invalid('Packed proposal data exceeds the byte limit');
51
+ assertActive(signal);
52
+ const output = [];
53
+ let totalBytes = 0;
54
+ const pushPng = (bytes) => {
55
+ assertActive(signal);
56
+ if (bytes.byteLength > objectProposalLimits.presentationBytes)
57
+ throw invalid('Proposal presentation image exceeds the byte limit');
58
+ totalBytes += bytes.byteLength;
59
+ if (totalBytes > objectProposalLimits.presentationBytes)
60
+ throw invalid('Proposal presentation exceeds the total byte limit');
61
+ output.push(new Uint8Array(bytes));
62
+ };
63
+ const seedSvg = Buffer.from(`<svg width="${image.width}" height="${image.height}" xmlns="http://www.w3.org/2000/svg"><rect x="${seedRegion.left}" y="${seedRegion.top}" width="${seedRegion.width}" height="${seedRegion.height}" fill="none" stroke="#1677ff" stroke-width="2"/></svg>`);
64
+ const seedPng = await encode(sharp(source, {
65
+ raw: { width: image.width, height: image.height, channels: 4 },
66
+ limitInputPixels: objectProposalLimits.imagePixels,
67
+ })
68
+ .composite([{ input: seedSvg }])
69
+ .png({ compressionLevel: 6 }), signal);
70
+ pushPng(seedPng);
71
+ if (snapshots.length === 1) {
72
+ const proposal = snapshots[0];
73
+ if (!proposal)
74
+ throw invalid('Singleton proposal disappeared during presentation');
75
+ const raster = unpackBinaryMask({
76
+ data: proposal.data,
77
+ width: image.width,
78
+ height: image.height,
79
+ format: 'binary-lsb0',
80
+ });
81
+ const overlay = makeOverlay(source, raster, image.width, image.height, proposal.bounds);
82
+ const overlayPng = await encode(sharp(overlay, {
83
+ raw: { width: image.width, height: image.height, channels: 4 },
84
+ limitInputPixels: objectProposalLimits.imagePixels,
85
+ }).png({ compressionLevel: 6 }), signal);
86
+ pushPng(overlayPng);
87
+ if (output.length !== 2)
88
+ throw invalid('Singleton presentation returned an invalid image count');
89
+ return output;
90
+ }
91
+ const sourcePreview = await encode(sharp(source, {
92
+ raw: { width: image.width, height: image.height, channels: 4 },
93
+ limitInputPixels: objectProposalLimits.imagePixels,
94
+ })
95
+ .resize(previewWidth, previewHeight, {
96
+ fit: 'contain',
97
+ background: { r: 20, g: 24, b: 32, alpha: 1 },
98
+ })
99
+ .png(), signal);
100
+ const sheetCount = Math.ceil(snapshots.length / 12);
101
+ for (let sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {
102
+ assertActive(signal);
103
+ const sheet = Buffer.alloc(sheetWidth * sheetHeight * 4, 0);
104
+ const layers = [];
105
+ const countOnSheet = Math.min(12, snapshots.length - sheetIndex * 12);
106
+ for (let position = 0; position < countOnSheet; position++) {
107
+ const proposal = snapshots[sheetIndex * 12 + position];
108
+ if (!proposal)
109
+ throw invalid('Proposal list changed during presentation');
110
+ assertActive(signal);
111
+ const tileX = (position % columns) * tileWidth;
112
+ const tileY = Math.floor(position / columns) * tileHeight;
113
+ const rectangle = proposal.bounds
114
+ ? `RECT [${proposal.bounds.left}, ${proposal.bounds.top}, ${proposal.bounds.width}, ${proposal.bounds.height}]`
115
+ : 'RECT empty';
116
+ const title = svg(tileWidth, 40, `<rect width="300" height="40" fill="#f2f4f7"/><text x="8" y="16" font-family="sans-serif" font-size="14" fill="#111">${proposal.id}</text><text x="8" y="33" font-family="sans-serif" font-size="12" fill="#111">${rectangle}</text>`);
117
+ layers.push({ input: title, left: tileX, top: tileY });
118
+ const raster = unpackBinaryMask({
119
+ data: proposal.data,
120
+ width: image.width,
121
+ height: image.height,
122
+ format: 'binary-lsb0',
123
+ });
124
+ const bounds = proposal.bounds;
125
+ if (bounds !== null) {
126
+ const maskOverlay = makeOverlay(source, raster, image.width, image.height, bounds);
127
+ const overlayPreview = await encode(sharp(maskOverlay, {
128
+ raw: { width: image.width, height: image.height, channels: 4 },
129
+ limitInputPixels: objectProposalLimits.imagePixels,
130
+ })
131
+ .resize(previewWidth, previewHeight, {
132
+ fit: 'contain',
133
+ background: { r: 20, g: 24, b: 32, alpha: 1 },
134
+ })
135
+ .png(), signal);
136
+ assertActive(signal);
137
+ layers.push({
138
+ input: sourcePreview,
139
+ left: tileX + previewLeft,
140
+ top: tileY + previewTop + 12,
141
+ });
142
+ layers.push({
143
+ input: overlayPreview,
144
+ left: tileX + previewLeft,
145
+ top: tileY + previewTop + 12,
146
+ });
147
+ const cutout = makeCutout(source, raster, image.width, image.height, bounds);
148
+ const cutoutPng = await encode(sharp(cutout.data, {
149
+ raw: { width: cutout.width, height: cutout.height, channels: 4 },
150
+ })
151
+ .resize(previewWidth, previewHeight, {
152
+ fit: 'contain',
153
+ background: { r: 20, g: 24, b: 32, alpha: 0 },
154
+ })
155
+ .png(), signal);
156
+ assertActive(signal);
157
+ layers.push({
158
+ input: cutoutPng,
159
+ left: tileX + previewLeft + previewWidth + 8,
160
+ top: tileY + previewTop + 12,
161
+ });
162
+ }
163
+ else {
164
+ layers.push({
165
+ input: sourcePreview,
166
+ left: tileX + previewLeft,
167
+ top: tileY + previewTop + 12,
168
+ });
169
+ layers.push({
170
+ input: svg(previewWidth, previewHeight, '<rect width="142" height="200" fill="#f2f4f7"/><text x="71" y="104" text-anchor="middle" font-family="sans-serif" font-size="16" fill="#555">empty</text>'),
171
+ left: tileX + previewLeft + previewWidth + 8,
172
+ top: tileY + previewTop + 12,
173
+ });
174
+ }
175
+ }
176
+ const sheetPng = await encode(sharp(sheet, {
177
+ raw: { width: sheetWidth, height: sheetHeight, channels: 4 },
178
+ })
179
+ .composite(layers)
180
+ .png({ compressionLevel: 6 }), signal);
181
+ pushPng(sheetPng);
182
+ }
183
+ if (output.length !== sheetCount + 1 ||
184
+ output.length > objectProposalLimits.presentationImages)
185
+ throw invalid('Proposal presentation returned an invalid image count');
186
+ return output;
187
+ },
188
+ };
189
+ }
190
+ function assertSignal(signal) {
191
+ if (!(signal instanceof AbortSignal))
192
+ throw invalid('signal must be an AbortSignal');
193
+ assertActive(signal);
194
+ }
195
+ function assertImage(image) {
196
+ if (image.format !== 'rgba8' ||
197
+ !Number.isSafeInteger(image.width) ||
198
+ !Number.isSafeInteger(image.height) ||
199
+ image.width < 1 ||
200
+ image.height < 1 ||
201
+ image.width * image.height > objectProposalLimits.imagePixels ||
202
+ image.data.length !== image.width * image.height * 4)
203
+ throw invalid('Expected canonical RGBA image within proposal limits');
204
+ }
205
+ function assertRect(rect, width, height, name) {
206
+ if (!rect ||
207
+ !Number.isSafeInteger(rect.left) ||
208
+ !Number.isSafeInteger(rect.top) ||
209
+ !Number.isSafeInteger(rect.width) ||
210
+ !Number.isSafeInteger(rect.height) ||
211
+ rect.left < 0 ||
212
+ rect.top < 0 ||
213
+ rect.width < 1 ||
214
+ rect.height < 1 ||
215
+ rect.left + rect.width > width ||
216
+ rect.top + rect.height > height)
217
+ throw invalid(`${name} is outside the source image`);
218
+ }
219
+ function snapshotProposal(proposal, image) {
220
+ const id = proposal.id;
221
+ const mask = proposal.mask;
222
+ const expectedBytes = Math.ceil((image.width * image.height) / 8);
223
+ if (!/^C(?:0[0-9]|[12][0-9]|3[0-5])$/.test(id) ||
224
+ !mask ||
225
+ mask.format !== 'binary-lsb0' ||
226
+ mask.width !== image.width ||
227
+ mask.height !== image.height ||
228
+ !(mask.data instanceof Uint8Array) ||
229
+ mask.data.byteLength !== expectedBytes)
230
+ throw invalid('Object proposal aliases or packed mask dimensions are invalid');
231
+ const data = new Uint8Array(mask.data);
232
+ const decoded = unpackBinaryMask({
233
+ data,
234
+ width: image.width,
235
+ height: image.height,
236
+ format: 'binary-lsb0',
237
+ });
238
+ const bounds = maskBounds(decoded, image.width, image.height);
239
+ if (bounds === null) {
240
+ if (proposal.region !== null)
241
+ throw invalid('Empty proposal must have a null pixel region');
242
+ }
243
+ else if (proposal.region !== null) {
244
+ assertRect(proposal.region, image.width, image.height, 'proposal region');
245
+ if (proposal.region.left !== bounds.left ||
246
+ proposal.region.top !== bounds.top ||
247
+ proposal.region.width !== bounds.width ||
248
+ proposal.region.height !== bounds.height)
249
+ throw invalid('Proposal region does not match packed mask support');
250
+ }
251
+ return {
252
+ id,
253
+ data,
254
+ region: bounds ? { ...bounds } : null,
255
+ bounds: bounds ? { ...bounds } : null,
256
+ };
257
+ }
258
+ function maskBounds(mask, width, height) {
259
+ let left = width;
260
+ let top = height;
261
+ let right = -1;
262
+ let bottom = -1;
263
+ for (let y = 0; y < height; y++) {
264
+ for (let x = 0; x < width; x++) {
265
+ if (mask[y * width + x] === 0)
266
+ continue;
267
+ if (x < left)
268
+ left = x;
269
+ if (x > right)
270
+ right = x;
271
+ if (y < top)
272
+ top = y;
273
+ if (y > bottom)
274
+ bottom = y;
275
+ }
276
+ }
277
+ return right < left || bottom < top
278
+ ? null
279
+ : { left, top, width: right - left + 1, height: bottom - top + 1 };
280
+ }
281
+ function makeOverlay(source, mask, width, height, bounds) {
282
+ const output = new Uint8Array(source.length);
283
+ for (let y = 0; y < height; y++) {
284
+ for (let x = 0; x < width; x++) {
285
+ const index = y * width + x;
286
+ const offset = index * 4;
287
+ output[offset] = source[offset];
288
+ output[offset + 1] = source[offset + 1];
289
+ output[offset + 2] = source[offset + 2];
290
+ output[offset + 3] = source[offset + 3];
291
+ if (mask[index] === 0)
292
+ continue;
293
+ const edge = x === 0 ||
294
+ y === 0 ||
295
+ x === width - 1 ||
296
+ y === height - 1 ||
297
+ mask[index - 1] === 0 ||
298
+ mask[index + 1] === 0 ||
299
+ mask[index - width] === 0 ||
300
+ mask[index + width] === 0;
301
+ if (edge) {
302
+ output[offset] = 0;
303
+ output[offset + 1] = 255;
304
+ output[offset + 2] = 255;
305
+ output[offset + 3] = 255;
306
+ }
307
+ else {
308
+ output[offset] = Math.round(source[offset] * 0.65 + 255 * 0.35);
309
+ output[offset + 1] = Math.round(source[offset + 1] * 0.65 + 32 * 0.35);
310
+ output[offset + 2] = Math.round(source[offset + 2] * 0.65 + 32 * 0.35);
311
+ }
312
+ }
313
+ }
314
+ if (bounds !== null) {
315
+ const right = bounds.left + bounds.width - 1;
316
+ const bottom = bounds.top + bounds.height - 1;
317
+ for (let x = bounds.left; x <= right; x++) {
318
+ paintRectanglePixel(output, width, x, bounds.top);
319
+ paintRectanglePixel(output, width, x, bottom);
320
+ }
321
+ for (let y = bounds.top; y <= bottom; y++) {
322
+ paintRectanglePixel(output, width, bounds.left, y);
323
+ paintRectanglePixel(output, width, right, y);
324
+ }
325
+ }
326
+ return output;
327
+ }
328
+ function paintRectanglePixel(output, width, x, y) {
329
+ const offset = (y * width + x) * 4;
330
+ output[offset] = 255;
331
+ output[offset + 1] = 220;
332
+ output[offset + 2] = 0;
333
+ output[offset + 3] = 255;
334
+ }
335
+ function makeCutout(source, mask, width, height, bounds) {
336
+ const data = new Uint8Array(bounds.width * bounds.height * 4);
337
+ for (let y = 0; y < bounds.height; y++) {
338
+ for (let x = 0; x < bounds.width; x++) {
339
+ const sourceIndex = (bounds.top + y) * width + bounds.left + x;
340
+ const outputIndex = (y * bounds.width + x) * 4;
341
+ if (mask[sourceIndex] === 0)
342
+ continue;
343
+ data[outputIndex] = source[sourceIndex * 4];
344
+ data[outputIndex + 1] = source[sourceIndex * 4 + 1];
345
+ data[outputIndex + 2] = source[sourceIndex * 4 + 2];
346
+ data[outputIndex + 3] = source[sourceIndex * 4 + 3];
347
+ }
348
+ }
349
+ return { data, width: bounds.width, height: bounds.height };
350
+ }
351
+ function svg(width, height, body) {
352
+ return Buffer.from(`<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">${body}</svg>`);
353
+ }
354
+ async function encode(pipeline, signal) {
355
+ try {
356
+ const bytes = await pipeline.toBuffer();
357
+ assertActive(signal);
358
+ return bytes;
359
+ }
360
+ catch (error) {
361
+ if (error instanceof ClassifierError)
362
+ throw error;
363
+ throw new ClassifierError('PROVIDER', 'Object proposal presentation failed');
364
+ }
365
+ finally {
366
+ pipeline.destroy();
367
+ }
368
+ }
369
+ function invalid(message) {
370
+ return new ClassifierError('INVALID_RESPONSE', message);
371
+ }
@@ -0,0 +1,4 @@
1
+ export declare function verifyPinnedAsset(path: string, expected: {
2
+ bytes: number;
3
+ sha256: string;
4
+ }, label: string): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { open } from 'node:fs/promises';
3
+ import { ClassifierError } from '../../entities/errors.js';
4
+ export async function verifyPinnedAsset(path, expected, label) {
5
+ let handle;
6
+ try {
7
+ handle = await open(path, 'r');
8
+ const stat = await handle.stat();
9
+ if (!stat.isFile() || stat.size !== expected.bytes)
10
+ throw new ClassifierError('UNSUPPORTED', `${label} asset is missing or has the wrong size`);
11
+ const bytes = Buffer.alloc(expected.bytes);
12
+ let offset = 0;
13
+ while (offset < bytes.length) {
14
+ const read = await handle.read(bytes, offset, bytes.length - offset, null);
15
+ if (!read.bytesRead)
16
+ throw new ClassifierError('UNSUPPORTED', `${label} asset read was incomplete`);
17
+ offset += read.bytesRead;
18
+ }
19
+ if ((await handle.read(Buffer.alloc(1), 0, 1, null)).bytesRead !== 0)
20
+ throw new ClassifierError('UNSUPPORTED', `${label} asset changed while being read`);
21
+ if (createHash('sha256').update(bytes).digest('hex') !== expected.sha256)
22
+ throw new ClassifierError('UNSUPPORTED', `${label} asset SHA256 does not match the pinned artifact`);
23
+ }
24
+ catch (error) {
25
+ if (error instanceof ClassifierError)
26
+ throw error;
27
+ throw new ClassifierError('UNSUPPORTED', `${label} asset cannot be read`);
28
+ }
29
+ finally {
30
+ await handle?.close();
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ import type { PixelRect } from '../entities/contracts.js';
2
+ import type { ImageRepresentationPort, RepresentationView } from '../entities/representation.js';
3
+ export interface TextPixelView {
4
+ id: string;
5
+ role: RepresentationView['role'];
6
+ sourceRegion: PixelRect;
7
+ width: number;
8
+ height: number;
9
+ /** Clean, resized, row-major RGBA8. Grid labels remain separately available as metadata. */
10
+ data: Uint8Array;
11
+ grid?: RepresentationView['grid'];
12
+ }
13
+ export interface TextRepresentationEncoder {
14
+ readonly profile: string;
15
+ /** Hard UTF-8 upper bound for one encoded view at these dimensions. */
16
+ maxEncodedBytes(width: number, height: number): number;
17
+ encode(view: TextPixelView, signal: AbortSignal): Promise<string>;
18
+ }
19
+ /**
20
+ * Build request-wide text representations from clean canonical pixels. The injected encoder
21
+ * is intentionally internal so research codecs do not become public package exports.
22
+ */
23
+ export declare function createTextRepresentation(encoder: TextRepresentationEncoder): ImageRepresentationPort;
@@ -0,0 +1,226 @@
1
+ import sharp from 'sharp';
2
+ import { limits } from '../entities/contracts.js';
3
+ import { ClassifierError, assertActive } from '../entities/errors.js';
4
+ import { representationLimits } from '../entities/representation.js';
5
+ import { readCanonicalPixels } from './raster.js';
6
+ function isContained(outer, inner) {
7
+ return (inner.left >= outer.left &&
8
+ inner.top >= outer.top &&
9
+ inner.left + inner.width <= outer.left + outer.width &&
10
+ inner.top + inner.height <= outer.top + outer.height);
11
+ }
12
+ function key(region) {
13
+ return `${region.left},${region.top},${region.width},${region.height}`;
14
+ }
15
+ function dimensions(region, maxSide) {
16
+ const scale = Math.min(1, maxSide / Math.max(region.width, region.height));
17
+ return {
18
+ width: Math.max(1, Math.round(region.width * scale)),
19
+ height: Math.max(1, Math.round(region.height * scale)),
20
+ };
21
+ }
22
+ function assertGridGeometry(view, width, height) {
23
+ if (!view.grid)
24
+ return;
25
+ const map = (rect) => ({
26
+ left: Math.round(((rect.left - view.sourceRegion.left) * width) / view.sourceRegion.width),
27
+ top: Math.round(((rect.top - view.sourceRegion.top) * height) / view.sourceRegion.height),
28
+ width: Math.round((rect.width * width) / view.sourceRegion.width),
29
+ height: Math.round((rect.height * height) / view.sourceRegion.height),
30
+ });
31
+ const mapped = view.grid.cells.map((cell) => map(cell.region));
32
+ if (view.grid.cells.some((cell) => !isContained(view.sourceRegion, cell.region)))
33
+ throw new ClassifierError('INVALID_INPUT', 'Grid geometry lies outside its source region');
34
+ if (mapped.some((rect) => rect.width < 1 || rect.height < 1))
35
+ throw new ClassifierError('RESOURCE_LIMIT', 'Distinct grid geometry is indistinguishable at the allocated resolution', { component: 'representation' });
36
+ const sourceKeys = new Set(view.grid.cells.map((cell) => key(cell.region)));
37
+ const mappedKeys = new Set(mapped.map((rect) => key(rect)));
38
+ if (sourceKeys.size !== mappedKeys.size)
39
+ throw new ClassifierError('RESOURCE_LIMIT', 'Distinct grid geometry collapses at the allocated resolution', {
40
+ component: 'representation',
41
+ });
42
+ }
43
+ async function cleanPixels(source, sourcePixels, view, width, height, signal) {
44
+ const region = view.sourceRegion;
45
+ if (!isContained({ left: 0, top: 0, width: source.width, height: source.height }, region))
46
+ throw new ClassifierError('INVALID_INPUT', 'Representation view lies outside the oriented source');
47
+ const raw = Buffer.from(sourcePixels.buffer, sourcePixels.byteOffset, sourcePixels.byteLength);
48
+ const pipeline = sharp(raw, {
49
+ raw: { width: source.width, height: source.height, channels: 4 },
50
+ limitInputPixels: limits.imagePixels,
51
+ })
52
+ .extract(region)
53
+ .resize(width, height, { kernel: 'nearest' })
54
+ .raw();
55
+ try {
56
+ const result = await pipeline.toBuffer({ resolveWithObject: true });
57
+ assertActive(signal);
58
+ if (result.info.channels !== 4 || result.data.byteLength !== width * height * 4)
59
+ throw new ClassifierError('INVALID_RESPONSE', 'Sharp returned an invalid canonical pixel view');
60
+ return {
61
+ id: view.id,
62
+ role: view.role,
63
+ sourceRegion: region,
64
+ width,
65
+ height,
66
+ data: result.data,
67
+ ...(view.grid ? { grid: view.grid } : {}),
68
+ };
69
+ }
70
+ finally {
71
+ pipeline.destroy();
72
+ }
73
+ }
74
+ function aliasesFor(view) {
75
+ return {
76
+ id: view.id,
77
+ role: view.role,
78
+ sourceRegion: view.sourceRegion,
79
+ ...(view.grid ? { grid: view.grid } : {}),
80
+ };
81
+ }
82
+ function gridMetadata(grid) {
83
+ if (!grid)
84
+ return undefined;
85
+ return {
86
+ cells: grid.cells.map((cell) => ({
87
+ id: cell.id,
88
+ column: cell.column,
89
+ row: cell.row,
90
+ region: cell.region,
91
+ label: grid.labels.get(cell.id) ?? null,
92
+ })),
93
+ };
94
+ }
95
+ function makePlan(groups, maxSide, encoder) {
96
+ const plan = [];
97
+ let totalBytes = 0;
98
+ for (const group of groups) {
99
+ const size = dimensions(group.sourceRegion, maxSide);
100
+ const metadataText = JSON.stringify({
101
+ representationView: {
102
+ id: group.aliases[0]?.id ?? group.primary.id,
103
+ aliases: group.aliases.map((alias) => ({
104
+ id: alias.id,
105
+ role: alias.role,
106
+ sourceRegion: alias.sourceRegion,
107
+ })),
108
+ sourceRegion: group.sourceRegion,
109
+ dimensions: size,
110
+ scaleX: size.width / group.sourceRegion.width,
111
+ scaleY: size.height / group.sourceRegion.height,
112
+ grid: group.aliases.flatMap((alias) => {
113
+ const value = gridMetadata(alias.grid);
114
+ return value === undefined ? [] : [{ viewId: alias.id, cells: value.cells }];
115
+ }),
116
+ },
117
+ });
118
+ const bound = encoder.maxEncodedBytes(size.width, size.height);
119
+ const metadataBytes = Buffer.byteLength(metadataText, 'utf8');
120
+ if (!Number.isSafeInteger(bound) ||
121
+ bound < 0 ||
122
+ bound > representationLimits.partCharacters ||
123
+ metadataText.length > representationLimits.partCharacters)
124
+ return undefined;
125
+ totalBytes += metadataBytes + bound;
126
+ plan.push({ group, ...size, metadataText, maxEncodedBytes: bound });
127
+ }
128
+ return totalBytes <= representationLimits.totalBytes ? plan : undefined;
129
+ }
130
+ function buildGroups(views) {
131
+ const groups = new Map();
132
+ for (const view of views) {
133
+ const regionKey = key(view.sourceRegion);
134
+ const prior = groups.get(regionKey);
135
+ if (!prior) {
136
+ groups.set(regionKey, {
137
+ sourceRegion: view.sourceRegion,
138
+ primary: view,
139
+ aliases: [aliasesFor(view)],
140
+ });
141
+ }
142
+ else {
143
+ prior.aliases.push(aliasesFor(view));
144
+ }
145
+ }
146
+ return [...groups.values()];
147
+ }
148
+ /**
149
+ * Build request-wide text representations from clean canonical pixels. The injected encoder
150
+ * is intentionally internal so research codecs do not become public package exports.
151
+ */
152
+ export function createTextRepresentation(encoder) {
153
+ return {
154
+ async prepare(input) {
155
+ assertActive(input.signal);
156
+ if (input.views.length > representationLimits.maxViews)
157
+ throw new ClassifierError('RESOURCE_LIMIT', 'A representation request may contain at most nine views', {
158
+ component: 'representation',
159
+ });
160
+ const groups = buildGroups(input.views);
161
+ for (const group of groups) {
162
+ if (!isContained({ left: 0, top: 0, width: input.source.width, height: input.source.height }, group.sourceRegion))
163
+ throw new ClassifierError('INVALID_INPUT', 'Representation view lies outside the oriented source');
164
+ }
165
+ let plan;
166
+ for (let side = representationLimits.maxSide; side >= 1 && plan === undefined; side -= 1)
167
+ plan = makePlan(groups, side, encoder);
168
+ if (!plan)
169
+ throw new ClassifierError('RESOURCE_LIMIT', 'No bounded text representation allocation fits the request', {
170
+ component: 'representation',
171
+ });
172
+ for (const item of plan)
173
+ for (const alias of item.group.aliases)
174
+ if (alias.grid)
175
+ assertGridGeometry({ ...item.group.primary, sourceRegion: item.group.sourceRegion, grid: alias.grid }, item.width, item.height);
176
+ const decoded = await readCanonicalPixels(input.source, input.signal);
177
+ const pixelViews = [];
178
+ for (const item of plan)
179
+ pixelViews.push(await cleanPixels(input.source, decoded.data, item.group.primary, item.width, item.height, input.signal));
180
+ const state = [];
181
+ const metadata = { profile: encoder.profile, bytes: 0, views: [] };
182
+ for (let index = 0; index < pixelViews.length; index += 1) {
183
+ const view = pixelViews[index];
184
+ const allocation = plan[index];
185
+ if (!view || !allocation)
186
+ throw new ClassifierError('INVALID_RESPONSE', 'Text representation allocation was incomplete');
187
+ assertActive(input.signal);
188
+ state.push(allocation.metadataText);
189
+ metadata.bytes += Buffer.byteLength(allocation.metadataText, 'utf8');
190
+ let text;
191
+ try {
192
+ text = await encoder.encode(view, input.signal);
193
+ }
194
+ catch (error) {
195
+ if (error instanceof ClassifierError && error.code === 'RESOURCE_LIMIT')
196
+ throw new ClassifierError(error.code, error.message, {
197
+ ...error.details,
198
+ component: 'representation',
199
+ });
200
+ throw error;
201
+ }
202
+ const bytes = Buffer.byteLength(text, 'utf8');
203
+ if (text.length > representationLimits.partCharacters || bytes > allocation.maxEncodedBytes)
204
+ throw new ClassifierError('RESOURCE_LIMIT', 'Encoder output exceeds its declared per-view bound', {
205
+ component: 'representation',
206
+ });
207
+ if (metadata.bytes + bytes > representationLimits.totalBytes)
208
+ throw new ClassifierError('RESOURCE_LIMIT', 'Encoded request exceeds the aggregate UTF-8 byte limit', {
209
+ component: 'representation',
210
+ });
211
+ metadata.bytes += bytes;
212
+ state.push(text);
213
+ metadata.views.push({
214
+ id: view.id,
215
+ sourceRegion: view.sourceRegion,
216
+ width: view.width,
217
+ height: view.height,
218
+ scaleX: view.width / view.sourceRegion.width,
219
+ scaleY: view.height / view.sourceRegion.height,
220
+ content: { left: 0, top: 0, width: view.width, height: view.height },
221
+ });
222
+ }
223
+ return { state, metadata };
224
+ },
225
+ };
226
+ }
@@ -61,4 +61,4 @@ export interface VisionChatOptions {
61
61
  maxResponseBytes?: number;
62
62
  }
63
63
  /** Ordinary text/image chat completion, with no native classifier/Decisions requirement. */
64
- export declare function createVisionChat(input: unknown): DecisionPort;
64
+ export declare function createVisionChat(input: unknown, modality?: 'vision' | 'text'): DecisionPort;