zorya-classifier 0.2.1 → 0.2.2

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 (126) hide show
  1. package/README.md +3 -3
  2. package/dist/app/classifier-config.d.ts +30 -0
  3. package/dist/app/classifier-config.js +46 -0
  4. package/dist/app/classifier-contracts.d.ts +32 -0
  5. package/dist/app/classifier-contracts.js +1 -0
  6. package/dist/app/classifier-representation.d.ts +5 -0
  7. package/dist/app/classifier-representation.js +33 -0
  8. package/dist/app/classifier-runtime.d.ts +7 -0
  9. package/dist/app/classifier-runtime.js +71 -0
  10. package/dist/app/classifier-spatial.d.ts +31 -0
  11. package/dist/app/classifier-spatial.js +78 -0
  12. package/dist/app/cli-arguments.d.ts +2 -2
  13. package/dist/app/create-classifier.d.ts +4 -34
  14. package/dist/app/create-classifier.js +37 -237
  15. package/dist/entities/classification.d.ts +98 -0
  16. package/dist/entities/classification.js +28 -0
  17. package/dist/entities/contracts.d.ts +6 -475
  18. package/dist/entities/contracts.js +6 -97
  19. package/dist/entities/decision.d.ts +211 -0
  20. package/dist/entities/decision.js +33 -0
  21. package/dist/entities/geometry.d.ts +37 -0
  22. package/dist/entities/geometry.js +26 -0
  23. package/dist/entities/image.d.ts +39 -0
  24. package/dist/entities/image.js +15 -0
  25. package/dist/entities/localization.d.ts +125 -0
  26. package/dist/entities/localization.js +13 -0
  27. package/dist/entities/segmentation-metadata.d.ts +70 -0
  28. package/dist/entities/segmentation-metadata.js +21 -1
  29. package/dist/entities/validation.d.ts +2 -0
  30. package/dist/entities/validation.js +9 -0
  31. package/dist/features/choice-beam-layout.js +2 -7
  32. package/dist/features/proposals/presentation.d.ts +21 -0
  33. package/dist/features/proposals/presentation.js +122 -0
  34. package/dist/features/proposals/validation.d.ts +138 -0
  35. package/dist/features/proposals/validation.js +125 -0
  36. package/dist/features/segmentation/box-check.js +10 -23
  37. package/dist/features/segmentation/mask.js +2 -7
  38. package/dist/features/segmentation/region-result.d.ts +18 -0
  39. package/dist/features/segmentation/region-result.js +171 -0
  40. package/dist/features/segmentation/source-mask.d.ts +6 -0
  41. package/dist/features/segmentation/source-mask.js +32 -0
  42. package/dist/features/segmentation/validation.d.ts +9 -0
  43. package/dist/features/segmentation/validation.js +94 -0
  44. package/dist/infrastructure/decision-transport/errors.d.ts +8 -0
  45. package/dist/infrastructure/decision-transport/errors.js +34 -0
  46. package/dist/infrastructure/decision-transport/images.d.ts +1 -0
  47. package/dist/infrastructure/decision-transport/images.js +52 -0
  48. package/dist/infrastructure/{openrouter-common.d.ts → decision-transport/request.d.ts} +6 -15
  49. package/dist/infrastructure/decision-transport/request.js +162 -0
  50. package/dist/infrastructure/decision-transport/response.d.ts +1 -0
  51. package/dist/infrastructure/decision-transport/response.js +72 -0
  52. package/dist/infrastructure/decision-transport/route-identity.d.ts +11 -0
  53. package/dist/infrastructure/decision-transport/route-identity.js +23 -0
  54. package/dist/infrastructure/openrouter-vision.js +1 -1
  55. package/dist/infrastructure/openrouter.js +7 -19
  56. package/dist/infrastructure/raster.js +3 -8
  57. package/dist/infrastructure/segmentation/grabcut/index.js +2 -4
  58. package/dist/infrastructure/segmentation/grabcut/masks.d.ts +49 -0
  59. package/dist/infrastructure/segmentation/grabcut/masks.js +89 -0
  60. package/dist/infrastructure/segmentation/grabcut/model-asset.d.ts +4 -0
  61. package/dist/infrastructure/segmentation/grabcut/model-asset.js +4 -0
  62. package/dist/infrastructure/segmentation/grabcut/runtime.d.ts +70 -0
  63. package/dist/infrastructure/segmentation/grabcut/runtime.js +96 -0
  64. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +4 -43
  65. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +4 -187
  66. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +3 -66
  67. package/dist/infrastructure/segmentation/mobile-sam/index.js +2 -478
  68. package/dist/infrastructure/segmentation/mobile-sam/input.d.ts +46 -0
  69. package/dist/infrastructure/segmentation/mobile-sam/input.js +83 -0
  70. package/dist/infrastructure/segmentation/mobile-sam/model-assets.d.ts +8 -0
  71. package/dist/infrastructure/segmentation/mobile-sam/model-assets.js +8 -0
  72. package/dist/infrastructure/segmentation/mobile-sam/object-proposals.d.ts +7 -0
  73. package/dist/infrastructure/segmentation/mobile-sam/object-proposals.js +124 -0
  74. package/dist/infrastructure/segmentation/mobile-sam/options.d.ts +43 -0
  75. package/dist/infrastructure/segmentation/mobile-sam/options.js +19 -0
  76. package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.d.ts +26 -0
  77. package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.js +71 -0
  78. package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.d.ts +112 -0
  79. package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.js +230 -0
  80. package/dist/infrastructure/segmentation/mobile-sam/segmenter.d.ts +22 -0
  81. package/dist/infrastructure/segmentation/mobile-sam/segmenter.js +268 -0
  82. package/dist/infrastructure/segmentation/mobile-sam/worker-expand.d.ts +7 -0
  83. package/dist/infrastructure/segmentation/mobile-sam/worker-expand.js +188 -0
  84. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +6 -149
  85. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +6 -845
  86. package/dist/infrastructure/segmentation/mobile-sam/worker-models.d.ts +14 -0
  87. package/dist/infrastructure/segmentation/mobile-sam/worker-models.js +72 -0
  88. package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.d.ts +7 -0
  89. package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.js +159 -0
  90. package/dist/infrastructure/segmentation/mobile-sam/worker-segment.d.ts +4 -0
  91. package/dist/infrastructure/segmentation/mobile-sam/worker-segment.js +126 -0
  92. package/dist/infrastructure/segmentation/native-protocol.d.ts +59 -34
  93. package/dist/infrastructure/segmentation/native-protocol.js +26 -16
  94. package/dist/infrastructure/segmentation/proposal-presentation-input.d.ts +20 -0
  95. package/dist/infrastructure/segmentation/proposal-presentation-input.js +107 -0
  96. package/dist/infrastructure/segmentation/proposal-presentation-pixels.d.ts +8 -0
  97. package/dist/infrastructure/segmentation/proposal-presentation-pixels.js +93 -0
  98. package/dist/infrastructure/segmentation/proposal-presentation.js +10 -201
  99. package/dist/infrastructure/vision-chat/options.d.ts +62 -0
  100. package/dist/infrastructure/vision-chat/options.js +46 -0
  101. package/dist/infrastructure/vision-chat/request.d.ts +23 -0
  102. package/dist/infrastructure/vision-chat/request.js +109 -0
  103. package/dist/infrastructure/vision-chat/response.d.ts +3 -0
  104. package/dist/infrastructure/vision-chat/response.js +105 -0
  105. package/dist/infrastructure/vision-chat.d.ts +4 -61
  106. package/dist/infrastructure/vision-chat.js +8 -271
  107. package/dist/openai-compatible.js +1 -1
  108. package/dist/processes/extract-from-region.js +11 -216
  109. package/dist/processes/extract.d.ts +2 -8
  110. package/dist/processes/extract.js +12 -128
  111. package/dist/processes/proposal-location.js +5 -251
  112. package/docs/README.ru.md +15 -12
  113. package/docs/agents.ru.md +95 -0
  114. package/docs/architecture.md +31 -5
  115. package/docs/architecture.ru.md +146 -0
  116. package/docs/benchmarks.md +3 -1
  117. package/docs/development.ru.md +141 -0
  118. package/docs/provenance.json +5 -1
  119. package/docs/publishing.md +5 -3
  120. package/docs/refactoring-plan.ru.md +183 -0
  121. package/package.json +4 -2
  122. package/dist/infrastructure/boundary-view.d.ts +0 -2
  123. package/dist/infrastructure/boundary-view.js +0 -263
  124. package/dist/infrastructure/openrouter-common.js +0 -317
  125. package/dist/infrastructure/text-representation.d.ts +0 -23
  126. package/dist/infrastructure/text-representation.js +0 -226
@@ -0,0 +1,183 @@
1
+ # План рефакторинга (волны 1–9)
2
+
3
+ Исторические планы и замороженные свидетельства в `docs/evidence/**` **не переписываются** задним числом. Полные потоки журналов хранятся **вне** репозитория (корень артефактов задачи рефакторинга); здесь — имена журналов/отчётов и измеренные итоги, подтверждённые проверками главного оркестратора.
4
+
5
+ Ветка: `work/classifier-refactor-20261023`. Порядок работы агентов: [правила для агентов](agents.ru.md), [контракт участников](https://github.com/zoomplus/zorya-classifier/blob/main/AGENTS.md).
6
+
7
+ ## Базовая линия до волн (сохранить)
8
+
9
+ | Метрика | Значение |
10
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | Ранний `npm test` (только `test/*.test.ts`) | **674** теста, **671** успех, **3** падения |
12
+ | Типичный блокер до волны `2A` | H6: журнал целостности — идентичность путей-алиасов: разные пути, ведущие через симлинки к одному и тому же файлу, считаются одним и тем же; воспроизведение через `realpath` |
13
+
14
+ Дальнейший рост переносимого каталога (772), восстановление идентичности путей, регрессия владения байтами рендерера, явная конфигурация учётных данных каталога `benchmarks` — отражены в волнах 2–7.
15
+
16
+ **Тройки чисел архива** в проверках `pack:check`: сначала **всего** файлов в установленном архиве, затем файлов **среды выполнения** (`dist` и связанные артефакты), затем деклараций, проверенных через **отдельный** проект-потребитель.
17
+
18
+ ### Пять зафиксированных регрессий/изменений поведения доказательств
19
+
20
+ 1. **Волна 2 / H6:** идентичность каталога-алиаса для воспроизведения целостности (`buildIntegrityReceiptByRealPath`, `test/v03-h6-integrity-receipt-path-identity.test.ts`): пути совпадают, если через симлинки они указывают на один и тот же реальный файл.
21
+ 2. **Рендерер:** Рендерер копирует RGBA-байты до чтения синхронного геттера `proposals`; отдельный тест проверяет мутацию исходного буфера этим геттером.
22
+ 3. **Историческая фикстура:** отделение синтетического манифеста «53 исходника / 82 ключа» от текущего набора исходников и списков зависимостей без переписывания замороженных хешей оценщика.
23
+ 4. **Будущие наборы манифестов исходников и списки зависимостей:** обновлены без изменения исторического JSON, хешей и порядка полей.
24
+ 5. **Учётные данные каталога `benchmarks`:** `benchmarks/credentials.ts`, переменные окружения процесса по умолчанию, необязательный `--env-file` только во вспомогательных раннерах.
25
+
26
+ ---
27
+
28
+ ## Волна 1 — базовая линия (ЗАВЕРШЕНО)
29
+
30
+ **Итог:** зафиксирован исходный набор тестов и список блокеров.
31
+
32
+ **Проверка:** базовая линия 674/671/3; ранние журналы в каталоге артефактов (сырые потоки не в репозитории).
33
+
34
+ ---
35
+
36
+ ## Волна 2 — фундамент (ЗАВЕРШЕНО)
37
+
38
+ **Модули:** `2A` — H6 и идентичность `realpath`; `2B` — проверка границ слоёв (`scripts/boundaries.mjs`), каталог тестов, `run-classified-tests.mjs`, `knip.json`, документация для участников разработки.
39
+
40
+ **Проверка (Linux Node 22, главный оркестратор):** коммит `c3923f0`; `npm run check` **764/764** — `wave2-root-checkpoint-retry.log`. Ранний сбой этапа Husky только на `format:check` — `wave2-root-checkpoint.log` (сохранён).
41
+
42
+ Узкие прогоны исполнителя Composer на встроенном Linux Node 24 — **не** полная продуктовая проверка Node 24.
43
+
44
+ ---
45
+
46
+ ## Волна 3 — контракты и листовая геометрия (ЗАВЕРШЕНО)
47
+
48
+ **Итог:** листовые модули `entities/*`, фасад `contracts.ts`; разделение схемы метаданных сегментации.
49
+
50
+ **Проверка:** коммит `0a5095f`; **764/764**; архив пакета 383 / 126 / 207 — `wave3-root-checkpoint.log`, `wave3-root-pack.log`.
51
+
52
+ ---
53
+
54
+ ## Волна 4 — разделение фабрики приложения (ЗАВЕРШЕНО)
55
+
56
+ **Итог:** `classifier-{contracts,config,runtime,spatial,representation}.ts` и тонкий `create-classifier.ts`.
57
+
58
+ **Проверка:** коммит `1515f15`; **764/764**; архив 393/136/208 — `wave4-root-pack.log`.
59
+
60
+ ---
61
+
62
+ ## Волна 5 — извлечение и предложения (ЗАВЕРШЕНО)
63
+
64
+ **Итог:** `features/segmentation/*`, `features/proposals/*`; процессы только оркестрируют.
65
+
66
+ **Проверка:** коммит `abc68b3`; **765/765** — отчёт проверенной ревизии (сохранён).
67
+
68
+ ---
69
+
70
+ ## Волна 6 — транспорты и нативная сегментация (ЗАВЕРШЕНО)
71
+
72
+ | Подволна | Зона ответственности | Проверка |
73
+ | -------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
74
+ | `6A` | `decision-transport/*`, `vision-chat/*`; удалён `openrouter-common.ts` | коммит `9e9b491`; **765/765**; архив **421** / **164** / **209**; API: 4 точки входа / 67 типов — `wave6a-root-checkpoint-retry.log`, `wave6a-root-pack.log` |
75
+ | `6B` | фасады `mobile-sam/*`, `grabcut/*` | коммит `e5b448b`; **765/765**; архив **449** / **192** / **212** — журналы подволны `6B` |
76
+
77
+ **Фикстура `6A`:** исторический тест манифеста использует замороженные ключи JSON и синтетические дайджесты — хеши замороженного оценщика не менять.
78
+
79
+ ---
80
+
81
+ ## Волна 7 — изоляция исследований, состав npm и инструменты (ЗАВЕРШЕНО)
82
+
83
+ | Подволна | Зона ответственности | Статус |
84
+ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
85
+ | `7A` | `benchmarks/experiments/{create-research-classifier,text-representation,boundary-view}`; `runtime-source-files.ts`; исследования вне дистрибутива npm | ЗАВЕРШЕНО — **767/767** — `wave7a-root-checkpoint.log`; коммит **`b7f8bd2`** |
86
+ | `7B` | `integrity-receipts.ts`; индекс H5 по `realpath`; `credentials.ts`; единый раннер; удалён `run-portable-tests.mjs` | ЗАВЕРШЕНО — **7/7** группы `local-evidence` после исправления; H4e сравнивается по реальному пути, сохранена проверка родительского каталога; журналы `wave7b-h4e-realpath-identity-local-evidence.log`, `wave7b-composer-h5-test.log`, **`final-local-evidence-node22-configured.log`** (7/7 при корректной раскладке данных); полные проверки главного оркестратора **772/772** после замены тестовых поддельных ключей на низкоэнтропийные фикстуры — `wave7b-root-checkpoint-retry.log`; сохранены неудачные журналы **`wave7-root-local-evidence-baseline.log`** (4/7 до исправления) и **`final-local-evidence-node22.log`** (3 падения `grounded-mask` при неверной конфигурации); коммит **`0fd9507`** |
87
+ | `7C` | явные точки входа Knip; удалены **4** неиспользуемые функции и **1** неиспользуемый тип; доказанно неиспользуемые экспорты сделаны внутренними; единственное исключение зависимости — `onnxruntime-node` | ЗАВЕРШЕНО — коммит **`76d7ac6`**; **772/772**; архив **445** / **188** / **212**; API: 4 точки входа / 67 типов / проверка совместимости типов — `wave7c-root-checkpoint.log`, `wave7c-root-pack.log` |
88
+
89
+ **Защищённая базовая линия (волна `7C`):** **407** файлов, включая **весь** JSON каталога `benchmarks`, побайтово идентичны на `76d7ac6`; `package-lock.json` без изменений. Отчёт **`wave7-final-protected-evidence.json`**. Число **377** относилось к более узкому подмножеству — не путать с 407.
90
+
91
+ **Учётные данные `7B`:** `process.env.OPENROUTER_API_KEY`; `--env-file` только во вспомогательных раннерах; без отката к чужому `.env`.
92
+
93
+ Алгоритмы классификации и `extract` **не менялись** в `7C`; удаления мёртвого кода уже выполнены — отдельной области работ после волны 9 на удаления в исходниках **не** планируется.
94
+
95
+ ---
96
+
97
+ ## Волна 8 — документация и происхождение (ЗАВЕРШЕНО)
98
+
99
+ **Зона ответственности:** связные русские руководства по архитектуре, разработке, агентам и этому плану; навигация в корневых руководствах и CONTRIBUTING, в [архитектуре на английском](architecture.md) и [эталонных прогонах](benchmarks.md); записи [provenance.json](provenance.json); указатели [контракта участников](https://github.com/zoomplus/zorya-classifier/blob/main/AGENTS.md) и [правила проекта](https://github.com/zoomplus/zorya-classifier/blob/main/.cursor/rules/zorya-classifier.mdc).
100
+
101
+ **Проверка:** коммит **`7a74226`**; Linux Node **22.23.2**, полный `npm run check` **772/772**, форматирование в этапе Husky — `wave8-root-checkpoint.log`; `pack:check` архив **447** / **188** / **212**, **0** HTTP-запросов к провайдеру (нет вызовов провайдера по HTTP; это не означает нулевой сетевой трафик) — `wave8-root-pack.log`; локальные ссылки, `git diff --check`, `npm run secrets:check`.
102
+
103
+ ---
104
+
105
+ ## Волна 9 — финальная верификация (ЗАВЕРШЕНО)
106
+
107
+ **Итог:** принята полная платформенная матрица на ревизии **`4877d05`** (ветка `work/classifier-refactor-20261023`). Сводка и имена журналов — в каталоге артефактов завершения `zorya-classifier-completion-20261023` (`local-verification.json`, `completion-code-verification.json`); сырые потоки **вне** репозитория.
108
+
109
+ **Узкое завершение кода на `4877d05` (не переписывает историю волн 1–8):** общая проверка границ источника запроса — результаты и упорядоченные ошибки Zod совпали на **420** входных комбинациях (`native-protocol-equivalence.json`). Только **два** теста H11 создают на CPU фикстуры FakeTensor только с метаданными (синтетические метаданные CUDA-устройства и dtype для отдельных случаев валидации и размещения в загрузчике); остальные тесты H11 FakeTensor не используют. Проверяется фактическая логика валидации и загрузчика рабочего кода и негативные случаи; прогон **не** доказывает выделение физической видеопамяти, реальный CUDA/GPU или живой инференс модели H11.
110
+
111
+ ### Исторический снимок на коммите `7a74226` (сохранён)
112
+
113
+ На этом снимке главный оркестратор измерил **только Linux**; Windows и **`python-native`** **ещё не запускались** — это исторический факт, его **не** переклассифицируют как «ново пройденное» на `7a74226`.
114
+
115
+ | Проверка | Результат | Журнал |
116
+ | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
117
+ | Linux Node **22.23.2**, `npm run check` | **772/772**, **0** пропусков, форматирование OK | `wave8-root-checkpoint.log` |
118
+ | Linux Node **22.23.2**, `pack:check` | **447** / **188** / **212**, **0** HTTP-запросов к провайдеру | `wave8-root-pack.log` |
119
+ | Linux Node **24.22.1** (измеренный исполняемый файл Node.js для Linux из установки Cursor Agent), `npm run check` | **772/772**, **0** пропусков | `final-node24-check.log`; формат — `final-node24-format.log` |
120
+ | Linux Node **24.22.1**, `pack:check` | **447** / **188** / **212**, **0** HTTP-запросов к провайдеру | `final-node24-pack.log` |
121
+ | Публичный API | **4** точки входа, **67** экспортируемых типов; типы экспортируемых значений совместимы в обе стороны с исходной версией | `final-public-api.log` |
122
+ | Защищённая базовая линия | **407** файлов без изменений плюс `package-lock.json`, `.env.example`, `src/index.ts`; кодовая контрольная точка **`76d7ac6`** | `final-protected-evidence.json` |
123
+ | Группа `local-evidence` (настроенная раскладка) | **7/7**, без падений/пропусков | `final-local-evidence-node22-configured.log` |
124
+ | Контрольные тесты геометрии на Python | **6/6** | `root-controlled-oracles.log` |
125
+
126
+ **Ограничения снимка `7a74226` (исторически):** Windows на Linux-хосте **не запускались**; удалённый GitHub Actions CI **не запускался**; **`python-native`** **не запускался**; **6/6** оракулов **не** заменяет `python-native`.
127
+
128
+ ### Текущая матрица на ревизии `4877d05` (принято)
129
+
130
+ | Платформа / Node | `npm run check` (переносимый каталог) | `pack:check` | Прочее |
131
+ | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | Linux **22.23.2** | **772/772**, **0** пропусков | **447** / **188** / **212**, **0** HTTP-запросов к провайдеру | формат в обычном commit-hook — `local-verification.json` |
133
+ | Linux **24.22.1** | **772/772**, **0** пропусков | те же тройки и байты архива | `linux-node24-check.log`, `linux-node24-pack.log`, `linux-node24-format.log` |
134
+ | Windows Server **2025** (build **26100**), Node **22.23.4** / npm **10.9.9**, задание `win-4877d0535a04-node22-r3` | **772** всего / **765** успех / **0** падений / **7** пропусков | **447** / **188** / **212**, **0** HTTP-запросов к провайдеру | локальная оценочная VM **win32** (удалённый GitHub Actions CI не запускался); все четыре команды exit **0**; **962/962** отслеживаемых исходников побайтово после проверок; журналы `windows/results/win-4877d0535a04-node22-r3/` |
135
+ | Windows Server **2025** (build **26100**), Node **24.22.1** / npm **11.19.1**, задание `win-4877d0535a04-node24` | **772** / **765** / **0** / **7** | идентично Linux | `windows/results/win-4877d0535a04-node24/`; артефакт декодирования консоли OEM **не** меняет имена тестов, счётчики и хеши исходников |
136
+
137
+ **Общий архив** (все четыре пары): SHA-256 **`cb3e980854bb625380b594f5ac3f05d657bbeda4b42880bf8f57fe307645aa20`**; SHA-256 каталога runtime **`dist`**: **`111c744ad406ff4645e2ff0f471610376a648aa81771eacf0a94bb61ea8d1131`**.
138
+
139
+ В колонках `pack:check` значение **0** HTTP означает ноль HTTP-запросов или HTTP-вызовов к провайдеру моделей, а не отсутствие любого сетевого трафика.
140
+
141
+ **Семь заранее существовавших пропусков на Windows** (не добавлены этой задачей; на Linux при наличии входов/возможностей платформы все **772** проходят):
142
+
143
+ 1. один тест с заменой POSIX FIFO;
144
+ 2. один тест с внешним симлинком без привилегий Windows;
145
+ 3. одна необязательная фикстура маски H3b;
146
+ 4. одна необязательная фикстура маски H4e;
147
+ 5. одна замороженная фикстура воспроизведения H6;
148
+ 6. одна замороженная фикстура отмены H6;
149
+ 7. один необязательный тест замороженных доказательств H8.
150
+
151
+ | Дополнительные проверки на Linux (`4877d05`) | Результат | Журнал / отчёт |
152
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
153
+ | Публичный API | **4** точки входа, **67** типов, совместимость типов экспортируемых значений **в обе стороны** | `public-api.log` |
154
+ | Защищённая базовая линия | **407** файлов без изменений плюс `package-lock.json`, `.env.example`, `src/index.ts` | `protected-evidence.json` |
155
+ | `python-native` (изолированный CPU Python **3.12.3**) | **44/44**, **0** пропусков; офлайн-контракты/метаданные/жизненный цикл — не проверяет GPU или живой H11 | успех: `python-native-rerun.log`; сбой: `python-native.log` |
156
+ | `local-evidence` (существующая раскладка вызывающего) | **7/7**, **0** падений, **0** пропусков | `local-evidence-current.log` |
157
+ | Контрольные оракулы геометрии Python | **6/6** | `controlled-oracles-current.log` |
158
+
159
+ Платные прогоны провайдера и публикация **не** выполнялись.
160
+
161
+ **Сохранённые неудачи и неверные конфигурации (имена только):** ранний сбой офлайн-настройки **`python-native`** (`python-native.log`); неверная конфигурация `local-evidence` — **`wave7-root-local-evidence-baseline.log`**, **`final-local-evidence-node22.log`**; Windows — сбой PowerShell-настройки до проверок (`win-4877d0535a04-node22`), отсутствие VC runtime на этапе Knip до переносимых тестов (`win-4877d0535a04-node22-r2`; typecheck/lint/boundaries уже прошли); первая проба Node `-e` с пустым stdout **не принята** (`windows/vc-runtime-first-provisioning.json`); принята последующая проверка загрузки модуля через отдельный CJS-файл с точным маркером stdout и exit **0** (`windows/vc-runtime-verified-smoke.json`). Установка VC++ x64 Redistributable — только в одноразовой VM, зависимости репозитория **не** менялись.
162
+
163
+ ---
164
+
165
+ ## Платформы и CI
166
+
167
+ | Тема | Статус |
168
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | Матрица CI (проект) | `ubuntu-latest`, `windows-latest` × Node 22 и 24 |
170
+ | Измерено (волна 9, Linux) | **`4877d05`**: Node **22.23.2** и **24.22.1** — `npm run check` **772/772**, **0** пропусков; `pack:check` **447** / **188** / **212**; исторический снимок **`7a74226`** — см. подраздел выше |
171
+ | Измерено (волна 9, Windows) | **`4877d05`**, локальная оценочная VM Server **2025** build **26100**, Node **22.23.4** и **24.22.1**: все проверки exit **0**; **765/772** с **7** заранее существовавшими пропусками (см. волну 9); байты архива как на Linux |
172
+ | Удалённый GitHub Actions CI | **Не запускался**; локальная Windows-матрица **не** заменяет удалённый CI |
173
+ | `python-native` | **`4877d05`**: **44/44**, **0** пропусков в изолированном CPU Python **3.12.3** (`python-native-rerun.log`, `python-runtime-receipt.json`); два теста H11 с FakeTensor-метаданными **не** проверяют GPU/живую модель; предварительные условия — [разработка](development.ru.md#python-windows-и-нативные-предварительные-условия) |
174
+ | Волна 8 | **ЗАВЕРШЕНО** — **`7a74226`** |
175
+
176
+ Успех удалённого GitHub Actions CI и публикация **не** заявлены этим документом.
177
+
178
+ ## Инварианты на все волны
179
+
180
+ - Публичные точки входа и семантика региона по умолчанию `v0.2` (закрытые проверки рамки в **текущем** основном потоке `extract`).
181
+ - Гигиена секретов: индексированные и рабочее дерево + байты установленного архива; только пустой `.env.example`.
182
+ - Без автоматической загрузки нативных весов; порты сегментатора и предложений на стороне вызывающего.
183
+ - Исторические метки и метрики `v0.2` остаются **историческими**; непроверенные рецепты не переклассифицировать как текущий проверенный поток.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zorya-classifier",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Provider-independent classification with experimental localization and object extraction for Node.js",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,7 +42,9 @@
42
42
  "lint": "eslint . --max-warnings 0",
43
43
  "format": "prettier --write .",
44
44
  "format:check": "prettier --check .",
45
- "test": "node --import tsx --test --test-concurrency=2 test/*.test.ts",
45
+ "test": "node --import tsx scripts/run-classified-tests.mjs portable",
46
+ "test:local-evidence": "node --import tsx scripts/run-classified-tests.mjs local-evidence",
47
+ "test:python-native": "node --import tsx scripts/run-classified-tests.mjs python-native",
46
48
  "check": "npm run secrets:check && npm run typecheck && npm run lint && npm run boundaries && npm run dead-code:ci && npm test && npm run build",
47
49
  "boundaries": "node scripts/boundaries.mjs",
48
50
  "dead-code:ci": "knip --no-progress",
@@ -1,2 +0,0 @@
1
- import type { BoundaryViewPort } from '../entities/boundary-view.js';
2
- export declare function createSharpBoundaryView(): BoundaryViewPort;
@@ -1,263 +0,0 @@
1
- import sharp from 'sharp';
2
- import { z } from 'zod';
3
- import { limits, pixelRectSchema } from '../entities/contracts.js';
4
- import { ClassifierError, assertActive } from '../entities/errors.js';
5
- const sourceSchema = z
6
- .object({
7
- bytes: z
8
- .custom((v) => v instanceof Uint8Array)
9
- .refine((v) => v.byteLength > 0 && v.byteLength <= limits.imageBytes),
10
- width: z.number().int().positive().max(16384),
11
- height: z.number().int().positive().max(16384),
12
- })
13
- .strict();
14
- const candidateSchema = z
15
- .object({
16
- id: z
17
- .string()
18
- .min(1)
19
- .max(128)
20
- .regex(/^[A-Za-z][A-Za-z0-9_-]*$/),
21
- display: z
22
- .string()
23
- .min(1)
24
- .max(8)
25
- .regex(/^[A-Za-z0-9_-]+$/),
26
- interval: z
27
- .object({
28
- start: z.number().int().nonnegative().safe(),
29
- endExclusive: z.number().int().positive().safe(),
30
- })
31
- .strict()
32
- .refine((v) => v.start < v.endExclusive),
33
- })
34
- .strict();
35
- const inputSchema = z
36
- .object({
37
- source: sourceSchema,
38
- sourceRegion: pixelRectSchema,
39
- axis: z.enum(['left', 'right', 'top', 'bottom']),
40
- candidates: z.array(candidateSchema).min(1).max(16),
41
- signal: z.custom((v) => v instanceof AbortSignal),
42
- })
43
- .strict();
44
- const maxContentSide = 1280;
45
- const maxOutputSide = 1567;
46
- const axisGutter = 210;
47
- const outerGutter = 10;
48
- const maxRenderedBytes = limits.imageBytes;
49
- function contained(outer, inner) {
50
- return (inner.left >= outer.left &&
51
- inner.top >= outer.top &&
52
- inner.left + inner.width <= outer.left + outer.width &&
53
- inner.top + inner.height <= outer.top + outer.height);
54
- }
55
- function escapeXml(value) {
56
- return value.replace(/[&<>"']/gu, (char) => {
57
- switch (char) {
58
- case '&':
59
- return '&amp;';
60
- case '<':
61
- return '&lt;';
62
- case '>':
63
- return '&gt;';
64
- case '"':
65
- return '&quot;';
66
- default:
67
- return '&apos;';
68
- }
69
- });
70
- }
71
- function failInput(message) {
72
- throw new ClassifierError('INVALID_INPUT', message, { component: 'boundary-view' });
73
- }
74
- function validate(input) {
75
- const parsed = inputSchema.safeParse(input);
76
- if (!parsed.success)
77
- failInput('Invalid boundary-view request');
78
- const value = parsed.data;
79
- if (value.source.width * value.source.height > limits.imagePixels)
80
- throw new ClassifierError('RESOURCE_LIMIT', 'Source raster exceeds pixel limits', {
81
- component: 'boundary-view',
82
- });
83
- const full = { left: 0, top: 0, width: value.source.width, height: value.source.height };
84
- if (!contained(full, value.sourceRegion))
85
- failInput('Source region lies outside the oriented image');
86
- const xAxis = value.axis === 'left' || value.axis === 'right';
87
- const axisSize = xAxis ? value.source.width : value.source.height;
88
- const axisStart = xAxis ? value.sourceRegion.left : value.sourceRegion.top;
89
- const axisEnd = axisStart + (xAxis ? value.sourceRegion.width : value.sourceRegion.height);
90
- const ids = new Set();
91
- const displays = new Set();
92
- let priorEnd = -1;
93
- for (const candidate of value.candidates) {
94
- if (ids.has(candidate.id) || displays.has(candidate.display))
95
- failInput('Boundary candidate IDs and display labels must be unique');
96
- ids.add(candidate.id);
97
- displays.add(candidate.display);
98
- if (candidate.interval.endExclusive > axisSize)
99
- failInput('Boundary interval lies outside the oriented source');
100
- if (candidate.interval.start < axisStart || candidate.interval.endExclusive > axisEnd)
101
- failInput('Boundary interval lies outside the source region');
102
- if (candidate.interval.start < priorEnd)
103
- failInput('Boundary intervals must be ordered and non-overlapping');
104
- priorEnd = candidate.interval.endExclusive;
105
- }
106
- return value;
107
- }
108
- function svgOverlay(axis, candidates, region, contentWidth, contentHeight, contentLeft, contentTop, outputWidth, outputHeight) {
109
- const vertical = axis === 'left' || axis === 'right';
110
- const axisLength = vertical ? region.width : region.height;
111
- const outputLength = vertical ? contentWidth : contentHeight;
112
- const sourceOffset = vertical ? region.left : region.top;
113
- const unit = outputLength / axisLength;
114
- const strokes = [];
115
- // Presentation is tied to the coordinate dimension, never to the semantic side being located.
116
- const color = vertical ? '#ff6b5f' : '#49d6a1';
117
- const title = vertical ? 'X coordinates' : 'Y coordinates';
118
- strokes.push(vertical
119
- ? `<text x="${outputWidth - outerGutter - 2}" y="${outerGutter + 10}" text-anchor="end" font-family="DejaVu Sans,sans-serif" font-size="9" fill="#f3f4f6">${title}</text>`
120
- : `<text x="${outerGutter}" y="${outerGutter + 10}" font-family="DejaVu Sans,sans-serif" font-size="9" fill="#f3f4f6">${title}</text>`);
121
- const positions = candidates.map((candidate) => {
122
- const begin = (candidate.interval.start - sourceOffset) * unit;
123
- const end = (candidate.interval.endExclusive - sourceOffset) * unit;
124
- return (begin + end) / 2;
125
- });
126
- const labelBaselines = vertical
127
- ? (() => {
128
- const pitch = 12;
129
- const lanes = [];
130
- const rightEdges = [];
131
- return candidates.map((candidate, index) => {
132
- const center = contentLeft + (positions[index] ?? 0);
133
- const width = candidate.display.length * 7 + 6;
134
- let lane = rightEdges.findIndex((right) => right + 3 <= center - width / 2);
135
- if (lane < 0) {
136
- lane = rightEdges.length;
137
- rightEdges.push(Number.NEGATIVE_INFINITY);
138
- }
139
- rightEdges[lane] = center + width / 2;
140
- lanes[index] = lane;
141
- return contentTop - 5 - (lane + 1) * pitch;
142
- });
143
- })()
144
- : (() => {
145
- const min = outerGutter + 10;
146
- const max = outputHeight - outerGutter - 3;
147
- const pitch = 12;
148
- const desired = positions.map((position) => contentTop + position + 4);
149
- const placed = desired.map((position) => Math.max(min, Math.min(max, position)));
150
- for (let index = 1; index < placed.length; index++)
151
- placed[index] = Math.max(placed[index] ?? min, (placed[index - 1] ?? min) + pitch);
152
- if ((placed.at(-1) ?? max) > max) {
153
- placed[placed.length - 1] = max;
154
- for (let index = placed.length - 2; index >= 0; index--)
155
- placed[index] = Math.min(placed[index] ?? max, (placed[index + 1] ?? max) - pitch);
156
- }
157
- return placed;
158
- })();
159
- for (const [index, candidate] of candidates.entries()) {
160
- const start = (candidate.interval.start - sourceOffset) * unit;
161
- const end = (candidate.interval.endExclusive - sourceOffset) * unit;
162
- const lo = Math.min(start, end);
163
- const hi = Math.max(start, end);
164
- const midpoint = (lo + hi) / 2;
165
- const label = escapeXml(candidate.display);
166
- if (vertical) {
167
- const center = contentLeft + midpoint;
168
- const halfWidth = candidate.display.length * 3.5 + 3;
169
- const labelCenter = Math.max(contentLeft + halfWidth, Math.min(contentLeft + contentWidth - halfWidth, center));
170
- const baseline = labelBaselines[index] ?? contentTop - 17;
171
- strokes.push(`<rect x="${contentLeft + lo}" y="${contentTop}" width="${Math.max(1, hi - lo)}" height="${contentHeight}" fill="${color}" fill-opacity="0.045"/>`, `<line x1="${contentLeft + start}" y1="${contentTop}" x2="${contentLeft + start}" y2="${contentTop + contentHeight}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<line x1="${contentLeft + end}" y1="${contentTop}" x2="${contentLeft + end}" y2="${contentTop + contentHeight}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<path d="M ${center} ${contentTop} L ${center} ${baseline + 4} L ${labelCenter} ${baseline + 4}" fill="none" stroke="${color}" stroke-width="1"/>`, `<text x="${labelCenter}" y="${baseline}" text-anchor="middle" font-family="DejaVu Sans,sans-serif" font-size="10" font-weight="bold" fill="${color}">${label}</text>`);
172
- }
173
- else {
174
- const midpointY = contentTop + midpoint;
175
- const baseline = labelBaselines[index] ?? midpointY + 4;
176
- const elbowX = contentLeft - 12 - index * 3;
177
- const labelX = contentLeft - 82;
178
- strokes.push(`<rect x="${contentLeft}" y="${contentTop + lo}" width="${contentWidth}" height="${Math.max(1, hi - lo)}" fill="${color}" fill-opacity="0.045"/>`, `<line x1="${contentLeft}" y1="${contentTop + start}" x2="${contentLeft + contentWidth}" y2="${contentTop + start}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<line x1="${contentLeft}" y1="${contentTop + end}" x2="${contentLeft + contentWidth}" y2="${contentTop + end}" stroke="${color}" stroke-opacity="0.9" stroke-width="2"/>`, `<path d="M ${contentLeft} ${midpointY} L ${elbowX} ${midpointY} L ${elbowX} ${baseline} L ${labelX + 4} ${baseline}" fill="none" stroke="${color}" stroke-width="1"/>`, `<text x="${labelX}" y="${baseline}" text-anchor="end" font-family="DejaVu Sans,sans-serif" font-size="10" font-weight="bold" fill="${color}">${label}</text>`);
179
- }
180
- }
181
- const sourceBounds = `<rect x="${contentLeft}" y="${contentTop}" width="${contentWidth}" height="${contentHeight}" fill="none" stroke="#f3f4f6" stroke-opacity="0.65" stroke-width="1"/>`;
182
- return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${outputWidth}" height="${outputHeight}">${sourceBounds}${strokes.join('')}</svg>`);
183
- }
184
- async function render(input) {
185
- assertActive(input.signal);
186
- const value = validate(input);
187
- assertActive(value.signal);
188
- // Sharp retains its input through an asynchronous pipeline; take ownership before awaiting.
189
- const ownedBytes = Buffer.from(value.source.bytes);
190
- const candidates = value.candidates.map((candidate) => ({
191
- id: candidate.id,
192
- display: candidate.display,
193
- interval: { ...candidate.interval },
194
- }));
195
- const { sourceRegion: region } = value;
196
- const scale = Math.min(maxContentSide / Math.max(region.width, region.height), Math.max(1, 768 / Math.min(region.width, region.height)));
197
- const contentWidth = Math.max(1, Math.round(region.width * scale));
198
- const contentHeight = Math.max(1, Math.round(region.height * scale));
199
- const vertical = value.axis === 'left' || value.axis === 'right';
200
- const contentLeft = vertical ? outerGutter : axisGutter;
201
- const contentTop = vertical ? axisGutter : outerGutter;
202
- const outputWidth = Math.max(280, contentLeft + contentWidth + outerGutter);
203
- const outputHeight = Math.max(280, contentTop + contentHeight + outerGutter);
204
- if (outputWidth > maxOutputSide || outputHeight > maxOutputSide)
205
- throw new ClassifierError('RESOURCE_LIMIT', 'Boundary view exceeds output side limit', {
206
- component: 'boundary-view',
207
- });
208
- const pixels = outputWidth * outputHeight;
209
- if (pixels > limits.imagePixels)
210
- throw new ClassifierError('RESOURCE_LIMIT', 'Boundary view exceeds output pixel limit', {
211
- component: 'boundary-view',
212
- });
213
- const overlay = svgOverlay(value.axis, candidates, region, contentWidth, contentHeight, contentLeft, contentTop, outputWidth, outputHeight);
214
- if (overlay.byteLength > 128 * 1024)
215
- throw new ClassifierError('RESOURCE_LIMIT', 'Boundary overlay exceeds byte budget', {
216
- component: 'boundary-view',
217
- });
218
- let pipeline;
219
- try {
220
- assertActive(value.signal);
221
- pipeline = sharp(ownedBytes, { limitInputPixels: limits.imagePixels, failOn: 'error' })
222
- .extract(region)
223
- .resize(contentWidth, contentHeight, { kernel: 'nearest' })
224
- .extend({
225
- left: contentLeft,
226
- top: contentTop,
227
- right: outputWidth - contentLeft - contentWidth,
228
- bottom: outputHeight - contentTop - contentHeight,
229
- background: { r: 17, g: 24, b: 39, alpha: 1 },
230
- })
231
- .composite([{ input: overlay }]);
232
- const result = await pipeline.png().toBuffer();
233
- assertActive(value.signal);
234
- if (result.byteLength > maxRenderedBytes)
235
- throw new ClassifierError('RESOURCE_LIMIT', 'Rendered boundary view exceeds byte limit', {
236
- component: 'boundary-view',
237
- });
238
- return {
239
- image: { bytes: result, width: outputWidth, height: outputHeight },
240
- sourceRegion: { ...region },
241
- content: { left: contentLeft, top: contentTop, width: contentWidth, height: contentHeight },
242
- scaleX: contentWidth / region.width,
243
- scaleY: contentHeight / region.height,
244
- candidates,
245
- };
246
- }
247
- catch (error) {
248
- assertActive(value.signal);
249
- if (error instanceof ClassifierError)
250
- throw error;
251
- throw new ClassifierError('INVALID_INPUT', 'Boundary view could not be rendered', {
252
- component: 'boundary-view',
253
- });
254
- }
255
- finally {
256
- pipeline?.destroy();
257
- ownedBytes.fill(0);
258
- overlay.fill(0);
259
- }
260
- }
261
- export function createSharpBoundaryView() {
262
- return { render };
263
- }