* chore(release): publish
* chore(release): publish
* ci: атомарный release.yaml, убрать workflow_run-связку (#366)
build-contracts + build-containers через workflow_run упёрлись в:
(а) default-branch caveat (новая логика не активна, пока не в main),
(б) `${{ github.event.workflow_run.head_sha }}` иногда пуст в YAML-
выражениях — описание см. в шаге Resolve tag, инцидент v2026.5.14
не дёрнул PRODUCTION_WEBHOOK_URL.
Замена — один `release.yaml` на push тэга `v*`: резолвит ветку через
`git branch --contains`, собирает контракты → пушит
`dicoop/contracts:<branch>`, собирает базу + сервисные образы →
пушит `dicoop/<svc>:<tag>`, шлёт webhook. Гонок нет by construction.
`build-contracts.yaml` остаётся только на push веток для CI-
обновления `dicoop/contracts:dev|testnet|main` без релизного тэга.
Триггер `tags: ['*']` и логика резолва ветки через --contains
оттуда удалены — это теперь забота release.yaml.
Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(release): publish
* chore(release): publish
* ci: tag-only триггеры для build-contracts и docs (#367)
build-contracts.yaml — только workflow_dispatch (ручная пересборка
`dicoop/contracts:<branch>` для отладки на dev-ноде). Тэги обрабатывает
release.yaml атомарно (контракты + контейнеры + webhook), отдельная
сборка по push'у в ветку только давала вторую параллельную сборку.
publish-docs.yaml и build-contracts-docs.yaml — на push:tags v* с
gate-job'ом, пропускающим только продакшн-тэги (без -alpha/-beta/-rc/
-test) на main. Раньше docs пересобирались на каждый push в
main/testnet/dev/reports/marketplace2 — впустую.
Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(capital/time-tracking): личные доли estimate, partial-split, revert при decline
Три бага в распределении билетов времени, вскрытые на прод-инциденте voskhod
(проект CC7-1 «Концепция», estimate=15 ч, 3 creators):
БАГ #1 — recalcDoneEstimatesForContributorProject раздавал «общий остаток пула»
(estimate − committed_total) / N всем creators поровну. Закоммитивший свою долю
получал её ещё раз, остальные — урезанную (15/3=5 → после committed 5 у одного
становилось 10/3=3.33 у каждого, включая того кто уже закоммитил).
Фикс: личная доля = estimate/N − собственный committed estimate. Введён общий
helper redistributeIssueEstimateEntries, который используют и applyExplicit-
EstimateToTimeEntries (force=true), и recalcDoneEstimates (force=false с
no-op оптимизацией если раскладка уже совпадает с планом).
БАГ #2 — commitTime при partial split (entry.hours > requested) создавал новую
committed-запись без entry_type и estimate_snapshot. По default'у БД сохраняла
её как entry_type='hourly', что ломало последующий recalc (он фильтрует только
entry_type='estimate'). Фикс: явно копировать entry_type и estimate_snapshot
из оригинального entry.
БАГ #3 — declineCommit / handleDeclineCommit меняли только commit.status в БД,
но не возвращали time-entries в is_committed=false. После отказа мастера часы
оставались в total_committed_hours и не возвращались в available_hours. Фикс:
новый метод revertEntriesForDeclinedCommit в TimeTrackingInteractor + методы
findCommittedByCommitHash / revertCommittedEntriesByCommitHash в TimeEntry-
Repository. После revert делается force=true redistribute для затронутых DONE
задач, чтобы доли вернулись в норму.
Покрытие тестами: 17 unit-тестов в time-tracking.interactor.spec.ts с
регрессионными сценариями под каждый из трёх багов плюс integration-сценарий
полного lifecycle CC7-1 (estimate → коммит → decline → revert).
---------
Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
migrate_voskhod_facts:
- accounts2[51]: 176 800 → 145 000 (минус 31 800 минП, теперь только деньги)
- accounts2[08]: 543 400 → 575 200 (плюс 31 800 минП — инвестировано в активы)
- accounts2[04/80/86] без изменений; Σ Dr = Σ Cr = 63 073 511 ✓
Новый COOPERATIVE-кошелёк w.sov.mnused для аналитики "использованные
минимальные паевые взносы" (Cr 80 source, перешедшие в 08). Для voskhod
31 800 размещается там вместо w.reg.minshr — обязательство перед пайщиками
на счёте 80 сохранено, но wallet-аналитика отражает что средства уже
ушли в долгосрочные активы.
migrator-048 (Phase 1 L3 → w.reg.minshr): skip voskhod явным if в TS +
жёсткий guard в migrate3 (eosio::check отвергает запись L3(voskhod,
w.reg.minshr) в prod). В testnet-сборке guard выключен — voskhod проходит
стандартный арифметический путь для видимости invariant-фейлов.
Реестр LEDGER2_WALLET_REGISTRY 13 → 14; namespace w.sov.* расширён до
"совет-level фондов" (целевое финансирование + использованные паевые).
TS-зеркало (wallets.generated.ts) регенерировано через pnpm gen:from-cpp;
snapshot-тест cooptypes обновлён.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
soviet::progwallets имеет blocked и membership_contribution как
binary_extension. На старых записях (например voskhod/enzqwnqsdqar/1)
eosjs возвращает undefined — pushAsset падает с
"Expected string containing asset". Защищаемся ?? zeroAssetLike(pw.available).
Для membership ?? уже был.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
У нас нет приватных ключей от кооперативов, которых мы мигрируем.
Поэтому ledger2::migrate3 и wallet::migrate3 теперь принимают auth от
get_self() (т.е. ledger2@active / wallet@active), и они же платят за RAM.
Mig-скрипты 047/048/049 пушат actions с актором = имя контракта.
Также правки в ledger2::migrate (Эпик 1) с прошлой сессии: default-case
для неизвестных legacy account IDs (862-867) — `break;` вместо
eosio_assert (ignore non-canonical), плюс IS_TESTNET-блок с clamp'ом
грязных legacy-данных.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Фильтр `status === 'confirmed'` пропускал всё: после Эпика 2 для program_id > 0
sndagreement пишет ""_n, а confirmagree больше не вызывается (воркфлоу ушёл
в wallet::signagree). Заменено на `status !== 'declined'`; wallet::migrate3
идемпотентен.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- controller: listener delta::ledger2::userwallets[w.cap.blago] синхронизирует regshare без ожидания scheduler-тика 1440 мин
- typeorm-ledger2-state.repository: getWallets/getAccounts берут самую свежую row на ключ и при present=false обнуляют суммы (не выкидывают), чтобы удалённый L2-кошелёк остался раскрываемым в реестре с историей
- capital: inline regshare после apprvappndx + whitelist auth в regshare
- programs.hpp: is_participant_of_cpp_by_program_id через wallet::users.programs[], get_program_wallet → has_program_wallet
- cooptypes/operations: human_name «Коммит РИД по программе Генератор», убрано «(перенос между кошельками)»
Контракты:
- wallet::signagree: auth = coopname OR contracts_whitelist; payer = подавший
auth (RAM-аккаунтинг). Снимает auth-блок при inline-вызове из capital.
- capital::regcontrib: gate Благорост-соглашения через wallet::users.programs[]
(ADR-008) вместо legacy progwallets; inline signagree от _capital@active —
атомарная связка payload → wallet::users → реестр.
- ledger2::migrate: compute_min_total_by_type через participant.minimum_amount
(фактический зафиксированный взнос пайщика), а не coop.minimum (мог быть
повышен после вступления — приводило к Σ L3 > L2 на w.reg.minshr). Убрано
неявное clamping → явный eosio::check + понятная ошибка.
- wallet::createwthd/declinewthd, capital::createpinv/createinvest/capauthwthd3:
Ledger2::apply на USER_SHARED-кошельках (REQUEST_WITHDRAW, DECLINE_WITHDRAW,
INVEST, WITHDRAW_FROM_CAPITAL).
- operations.hpp/processes.hpp: новые записи реестра.
cooptypes: зеркало новых operations/processes (o.cap.wthcap, o.wal.wthreq,
o.wal.wthdec, p.cap.wthcap).
Migrator: 048 пишет L3 по participant.minimum_amount; gate на
ledger2::meta.migrated до запуска (без миграции migrate3 разъезжается с L2).
Controller:
- ParticipantStatusSyncService: action::soviet::addpartcpnt → users.status='active'.
Без него ActiveUserStatusGuard блокирует свежепринятых пайщиков на
createDepositPayment и других мутациях — статус так и оставался '4_Registered'.
- migrations/V2.2.0: backfill users.status='active' по soviet::participants
из chain (через BLOCKCHAIN_RPC).
- registration-programs: «Программа Капитализация» → «Программа Благорост».
Desktop:
- useWalletStore: универсальный optimistic-overlay (applyOptimisticPatch /
TTL / clearOnLoad). program_wallets — теперь computed поверх raw-стейта;
серверный refetch перетирает overlay.
- CreateProgramInvest: оптимистично списывает ЦК и зачисляет blocked
Благороста до подтверждения цепочкой, ревертит при ошибке.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`LEDGER2_WALLET_REGISTRY` и `LEDGER2_USER_SHARED_PROGRAM_MAPPING` теперь
вытягиваются из `contracts/cpp/lib/core/ledger2/wallets.hpp` Node-парсером
(`scripts/gen-from-cpp.ts`) и пишутся в `src/ledger2/wallets.generated.ts`.
Источник истины — C++; TS — копия. Парсер строгий: на любую неясность кидает
ошибку. На страже — snapshot-тест в `test/wallets-registry.snapshot.test.ts`.
Запуск: `pnpm --filter cooptypes gen:from-cpp` (или автоматически через
`prebuild` хук перед `pnpm --filter cooptypes build`).
Удалены локальные копии маппинга:
- `controller/src/domain/wallet/utils/program-wallet-mapping.ts` (был дубликат)
- inline `PROGRAM_TO_WALLET` в `migrator/migrations/049_*.ts`
Потребители теперь импортируют `Ledger2.walletNamesForProgram`,
`Ledger2.programIdForWallet`, `Ledger2.MEMBERSHIP_WALLET_NAME`,
`Ledger2.ALL_PROGRAM_WALLET_NAMES` из cooptypes.
В первоначальной версии 049 для program_id=1 (ЦК) писался только
w.wal.share с available/blocked, а membership_contribution молча терялась
(на текущем prod она 0, но тест-стенды и будущие данные содержат
ненулевые значения).
Теперь для ЦК эмитим ДВА migrate3-вызова: w.wal.share (available/blocked)
и w.wal.member (membership_contribution). Если membership=0 —
ledger2::migrate3 (0,0) делает безопасный no-op.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>