Контракт capital::startproject:
- проверка project.master != '' (без мастера коммиты некому одобрять)
- для компонента (parent_hash != 0): родитель должен быть в статусе active
(иначе компонент окажется в работе под закрытым/незапущенным проектом)
Реестр операций (human_name → пользовательский язык):
- o.cap.cnvshr: «РИД → паевой взнос деньгами» → «РИД → главный кошелёк»
(operations.hpp + cooptypes/operations.ts + p.cap.rid.standard.yaml)
Документация:
- docs/new/blagorost/lifecycle.md — пользовательская доп-инфо в таблице
статусов и в «Что важно помнить»: запуск Компонента возможен только при
назначенном мастере и активном Проекте.
- docs-harness/scenarios/blagorost/{commits-master,master-and-plan}.mjs —
пометка для автора сценариев о пред-условиях запуска компонента.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше конвертация сегмента (`convertsegm`) была отдельным процессом
`p.cap.cnvseg` со своим `convert_hash` — но фактически это завершающая
фаза внесения РИД, а не самостоятельный процесс. Объединяем под
`p.cap.rid` с единым анкером `result_hash` от pushrslt до convertsegm.
Контракт capital:
- convertsegm.cpp перенесён convert_segment/ → push_result/, payload и
memo получают result_hash вместо convert_hash; перед apply проверяет
result.status == ACT2 + project_hash/username match.
- signact2 НЕ удаляет result — переводит в ACT2 (анкер до convertsegm),
delete_result переехал в convertsegm.
- segment-conversion-process.dox смержен в result-submission-process.dox
(новый шаг 8 + диаграмма + эффекты/документы).
ledger2:
- processes::capital::CNVSEG удалён, RID комментарий расширен.
- o.cap.cnvshr/o.cap.cnvbl: process_type → p.cap.rid.
cooptypes / SDK / controller:
- IConvertsegm.convert_hash → result_hash, regen + snapshot обновлён.
- DTO/domain/mutation-log: convert_hash → result_hash.
- PROCESS_HASH_LOCATOR: убран p.cap.cnvseg, p.cap.rid комментарий
расширен (4 операции + анкер result_hash).
- schema.gql + zeus regenerated.
Desktop:
- ConvertSegment: result_hash берётся из resultStore по
(username, project_hash), а не генерируется случайным.
Стандарт p.cap.rid:
- convertsegm как closer, новое state `converted` (final), transition
accepted → converted, scenario step 8, документ заявления конвертации
(registry_id TBD-Standardization), operations o.cap.cnvshr/cnvbl.
- o.cap.accept переведён на wallet_op NONE (без TRANSFER кошелька).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Без этих ключей integrity-check на старте падал, контроллер крашился:
'p.cap.wthcap, p.cap.cnvseg' пришли в OPERATION_CODE_TO_PROCESS_TYPE из
cooptypes (WITHDRAW_FROM_CAPITAL, CONVERT_TO_SHARE/CONVERT_TO_BLAGO).
- p.cap.wthcap → capital::prgwithdraws.withdraw_hash (жизнь запроса).
- p.cap.cnvseg → [] (одноактовый convertsegm; данные из blockchain_actions).
Контракт ledger2 удаляет L3-запись при обнулении (cleanup_l3_if_empty) и при
следующей операции на той же паре (wallet_name, username) выдаёт новый id.
Postgres-mirror хранит удалённые записи с present=false для версионирования —
полный unique idx_user_wallets_natural_key блокировал upsert новой row,
дельта депозита проваливалась с duplicate key, баланс в UI замирал.
- entity: @Index ... { unique: true, where: '"present" = true' }
- UserWalletIndexInitializer (OnModuleInit): пересоздаёт индекс как partial,
потому что TypeORM synchronize не сравнивает WHERE и держит обычный unique
Прямая инвестиция в Благорост не имеет сегмента, поэтому считаем energy_gain
от amount напрямую и вызываем add_energy_and_check_levelup. До патча уровень
не рос при createpinv — только при createinvest (через update_gamification_from_segment).
- 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 «Коммит РИД по программе Генератор», убрано «(перенос между кошельками)»
OpenSearch требователен по ресурсам и подвешивает локалку;
нужен редко — поднимаем вручную при необходимости.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ACCEPT_RID теперь оставляет кошелёк на w.cap.gen и пишет только Dr 04 / Cr 08;
кошельковое перемещение (на ЦК или Благорост) делается отдельным шагом
convertsegm. CONVERT_TO_SHARE/CONVERT_TO_BLAGO — TRANSFER без бухпроводок,
так как двойная проводка уже была сделана при ACCEPT_RID.
apply.cpp пропускает walletop при NONE; walletop.cpp режет op_code=5 на входе.
Static_assert none_pattern_correct() гарантирует пустые wallet_from/to и
обязательные debit/credit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
capital/balances: get_capital_program_*_share_balance читают L2/L3 ledger2
(wallets[BLAGOROST_FUND] и userwallets[(w.cap.blago, username)]) вместо
soviet::progwallets — фикс «Благорост = 0% после pushresult».
phase A: удалены прямые Wallet::add/sub/block/unblock_funds в 10 callers
(capital: signact2, act2pgprp, createinvest, createpinv, capauthwthd3,
importcontr; wallet: completewthd, completedpst, createwthd, declinewthd) —
во всех есть зеркальный Ledger2::apply, дубль создавал параллельный учёт
в legacy progwallets и разъезжался с L3 при первом же сбое.
phase B: convertsegm.cpp переписан на 2 × Ledger2::apply. В реестр операций
добавлены o.cap.cnvshr (TRANSFER GENERATOR_FUND→SHARE_FUND_PAY, Dr 80/Cr 08)
и o.cap.cnvbl (TRANSFER GENERATOR_FUND→BLAGOROST_FUND, Dr 04/Cr 08) +
новый процесс p.cap.cnvseg для аудит-следа отдельно от ACCEPT_RID.
desktop/WalletProgramWidget: блок «Заблокировано» рендерится только при
parseFloat(blocked) > 0; убран хак с подмешиванием minimum_amount к ЦК.
Marketplace вынесен в отдельный заход.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Сразу после chain-мутации parser2 → consumer → PG отстаёт на 1-3с, и
немедленный loadUserWallet возвращает ещё стейт до инвеста; внутри он
clearOptimisticPatches() — оптимистичный патч стирается, UI откатывается
к до-инвеста. Через ~3-5с какой-то фоновый refetch получает уже свежие
данные и UI снова прыгает на новое значение.
Откладываем рефетч на 4с (не await — fire-and-forget). За это время
дельта прилетает, refetch получает уже-после-инвеста, патч чисто
сменяется серверной правдой без промежуточного отката.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
controller/src/domain/wallet/enums/program-type.enum.ts маппит program_id=1
на ProgramType.MAIN ('main'), а не 'wallet' — это другой источник, чем
cooptypes/src/ledger2/programs.ts.internal_name. UI читает поле program_type
с бэкенда, поэтому фильтр и optimistic-патч должны использовать 'main'.
Без этого MicroWallet.find(program_type==='wallet') возвращал undefined
(в углу 0 вместо ЦК-баланса), а optimistic-патч на ЦК молча промахивался.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MicroWallet: вместо program_wallets[0] — find(program_type === 'wallet').
Порядок program_wallets от backend недетерминирован, в углу мог оказаться
Благорост / Генератор вместо ЦК.
- CreateProgramInvest: optimistic-патч на стороне Благорост был
program_type='capital' + blocked_delta — мисматч (internal_name='blagorost')
и не та полка (UI читает available из L3 ledger2::userwallets, поскольку
Ledger2::apply(INVEST) делает TRANSFER в .available; progwallets.blocked
десктоп не отображает). Поправлено на 'blagorost' + available_delta.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Контракты:
- 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>
Депозит-флоу wallet::completedpst → Wallet::add_available_funds →
soviet::addbal падал с «Кошелёк не найден», если у пайщика ещё не было
строки в soviet::progwallets для программы. Подписание соглашения
(wallet::signagree) progwallets-запись не открывает по дизайну (ADR-008,
state источника правды — wallet::users.programs[]).
addbal теперь делает upsert: если записи нет — создаёт с нулевыми
балансами и сразу прибавляет quantity. Если есть — стандартный modify.
agreement_id=0 (соглашения для legacy progwallets уже не нужны:
актуальная подпись лежит в wallet::users).
Subbal/blockbal/unblockbal/addmemberfee оставлены как есть — для них
запись должна существовать (нечего блокировать/списывать с пустого).
Манифест reports-расширения (controller/extensions.registry) — title и
title desktops с «Отчёты ФНС» на «Стол бухгалтера». Description
расширил под фактическое наполнение: реестры операций / проводок /
кошельков / счетов плюс налоговые формы.
* Расширение переименовано с «Отчёты ФНС» на «Стол бухгалтера» (заголовок
и breadcrumb).
* AccountsPage: Активный/Пассивный (тип счёта) и Дебет/Кредит (сторона
проводки) теперь theme-aware — светлая тема blue-grey-9 / brown-8,
тёмная blue-grey-3 / brown-3, weight bold. Старый text-blue-grey-8 /
text-brown-7 на тёмной теме читался плохо.
OperationsPage в onMounted, при ?operation_id=…, разворачивал строку через
expanded.set, но не дёргал loadChildOps — поэтому таблицы «Движения по
кошелькам» и «Проводки по счетам» оставались пустыми до ручного
схлопывания/раскрытия. Теперь после load() сразу подгружаем сибсов
найденного apply'а по его processHash.
Связь apply-orchestrator ↔ inline walletop/debit/credit строится точечно через
явные идентификаторы parser2: пара `(transaction_id, creator_action_ordinal)`
inline-action указывает на `(transaction_id, action_ordinal)` родителя.
* getPostings: парный credit подтягивается LEFT JOIN на (transaction_id,
creator_action_ordinal). Удалён эвристический алгоритм
«closest-credit-after-debit-without-apply-between». Родительский apply
тоже точечно через (transaction_id, action_ordinal=d.creator_action_ordinal)
без ограничения по name — ловит и revert как родителя inline-проводок.
* getHistory: parentApplyGlobalSequence в SELECT, applyGlobalSequence /
parentApplyGlobalSequence фильтры — все через те же точечные JOIN'ы.
Удалены multi-effect range-эвристики «между этим apply и следующим
с тем же processHash».
* Фильтр по accountId/walletName: для apply/revert — EXISTS-проверка
inline ребёнка (debit/credit или walletop) с этим account_id/wallet.
Прямое сравнение для самих debit/credit/walletop/walmove.
* Удалены мёртвые ветки `data->>'id'` (нет такого поля у ledger2-actions).
* Никаких fallback'ов: parser2 даёт creator_action_ordinal для каждого
inline нативно — отдельной ветки «если родитель не нашёлся, ищем
ближайший apply» нет.
* PostingsPage: «№ проводки» (debit.global_sequence) и «№ процесса»
(process_hash) — отдельные колонки, оба EntityIdBadge с hover-эффектом и
копированием по клику. Tooltip про парный credit убран.
* OperationsPage: «№ операции» (apply.global_sequence) и «№ процесса» —
отдельные колонки EntityIdBadge.
* CoopWalletsPage: «№» движения (walletop.global_sequence) — EntityIdBadge.
* AccountsPage (история проводок счёта): добавлены колонки «№ проводки»
(debit/credit.global_sequence) и «№ процесса». Cross-link «К операции»
теперь точечный — через operation_id (apply.global_sequence) вместо
process_hash, чтобы не открывалось несколько операций.
* Hint у поисковых input'ов убран (мешал стабильному layout).
Backend: добавлен parentApplyGlobalSequence в Ledger2Operation — для
точечного cross-link из debit/credit в реестр операций.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Используем существующий blockchain_actions.global_sequence (unique-индекс)
как канонический ID — без изменений контрактов и схемы БД.
* «№ операции» = apply.global_sequence (точечная адресация одного apply
+ его siblings; multi-effect-защита через диапазон до следующего apply
того же processHash).
* «№ проводки» = debit.global_sequence; парный credit подтянется
стандартным алгоритмом «closest-credit-after-debit-without-apply-between».
* «№ движения» = walletop.global_sequence.
Backend: новые фильтры applyGlobalSequence/walletopGlobalSequence в
getLedger2History и debitGlobalSequence/applyGlobalSequence в
getLedger2Postings. Frontend: колонки № операции (OperationsPage),
№ проводки (PostingsPage), № движения (CoopWalletsPage). Универсальный
search-input на каждой странице — определяет тип ID по формату ввода
(цифры → seq, hex64 → process_hash).
URL-параметры унифицированы: ?operation_id=apply.global_sequence,
?posting_id=debit.global_sequence, ?process_hash=hex64.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* AccountsPage / CoopWalletsPage: иконки кросс-линков в реестр операций
(fa-up-right-from-square / fa-list-ul) заменены на fa-arrow-right —
одинаково с ParticipantWalletsPage и PostingsPage.
* OperationsPage: убран text-grey-10 со столбца «Сумма» — на тёмной теме
тёмный шрифт сливался с фоном.
* ReportsCalendar / CalendarCell: все hex-цвета через rgba + body--dark
overrides. Раньше календарь оставался белым на тёмной теме.
* PostingsPage: убран UI-инпут поиска по process_hash + связанные chip /
filter / handler. Реестр операций — единственная точка поиска (там видно
и проводки, и движения по кошелькам в одной развёрнутой строке).
Cross-link account_id / username по query-параметру сохранён.
* WalletTransferDialog: добавлен #no-option слот и hint в q-select
«В кошелёк». Если у кооператива нет других кошельков на бух.счёте
источника, пользователь видит причину пустого списка, а не молчаливый
пустой dropdown.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Из строки проводки можно одним кликом провалиться в реестр операций
с фильтром по process_hash и раскрытой нужной apply-операцией. На больших
multi-effect процессах (несколько apply внутри одного process_hash) это
важно: query.operation_id ведёт ровно к parent apply этой проводки, а не
к первой попавшейся.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В предыдущем коммите оставил COALESCE «на случай walmove/revert». Проверил
ledger2.hpp — все actions (debit/credit/apply/walletop/walmove/revert) принимают
только amount. Поля quantity в data ledger2 нет ни у кого. Возвращаю один amount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ledger2::debit и ledger2::credit принимают только coopname/account_id/amount/
process_hash/memo (см. ledger2.hpp), поля username у них нет, а сумма лежит
под ключом amount, не quantity. Резолвер getLedger2Postings:
- quantity = COALESCE(d.data->>'amount', d.data->>'quantity') — последний
на случай legacy walmove/revert, у них quantity;
- username берётся из ближайшего parent apply того же process_hash;
- фильтр по username — тоже через parent apply (subquery).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше проводки (Дт/Кт/Сумма) были видны только при разворачивании
отдельной операции в реестре операций. Не было плоской ленты «все
проводки кооператива», для бухгалтерской сверки приходилось разворачивать
каждую операцию вручную.
Бэкенд: новый GraphQL Query getLedger2Postings(input). Резолвер
восстанавливает пары debit+credit из blockchain_actions по правилу
«ближайший parent apply того же process_hash» — multi-effect процесс с
несколькими apply внутри одного process_hash даёт несколько проводок,
каждая закрыта своим apply'ем. Серверные фильтры: accountId (попадание
в Дт ИЛИ Кт), processHash, username, dateFrom/dateTo. Пагинация.
Фронт: страница /reports/postings рядом с операциями/кошельками/счетами.
Колонки: Дата | ID процесса | Операция (chip с цветом по контракту) |
Дебет | Кредит | Сумма | Пайщик. Клик на хэш — копирует. Tooltip на
коде счёта показывает название (через AccountIdCell + getAccountName).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
При подключении кооператива в середине года все периоды до этой даты
показывались красным «просрочен» — пол-календаря в красном раздражает
и фактически некорректно: эти отчёты сдавать не надо.
Резолвер тянет registrator::accounts(coopname).registered_at и для ячеек
с dueDate < registered_at возвращает новый статус BEFORE_REGISTRATION.
Приоритет: ручные отметки (NOT_REQUIRED / SUBMITTED_EXTERNALLY) перебивают,
если пользователь уже что-то проставил.
Фронт рендерит ячейку нейтрально-серой, без hover-обводки и без клика —
открывать там нечего.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Реестр кошельков → Пайщики: ячейка с двумя суммами (доступно/заблокировано)
была без подписей и читалась как «две одинаковые цифры». Иконки coins/lock
не передавали смысл «открыто/закрыто».
- Иконки: fa-lock-open (доступно) ↔ fa-lock (заблокировано) — симметричная
пара, узнаваемая без объяснений.
- q-tooltip над каждой строкой: «Доступно» / «Заблокировано» (delay 200ms).
- value-zero / cell-dash: подняли контраст до rgba(0,0,0,0.55) на light и
rgba(255,255,255,0.7) на dark — раньше нули сливались с фоном на обеих
темах.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`WalletCell` в ParticipantWalletsPage рендерится через render-функцию
inline-компонента — у его h()-узлов нет data-v parent'а, поэтому
scoped + :deep до них не доходит стабильно. Цвета (var(--q-positive),
rgba для zero/dash) и body--dark overrides не применялись — на странице
оставался хардкод-вид по умолчанию.
Решение: вынести правила `.wallet-cell` в отдельный `<style lang="scss">`
(без scoped). Класс достаточно специфичен — конфликта с другими
компонентами не будет.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ADR: фронт не ходит в чейн напрямую. fetchTable(soviet::programs) был
оставлен в двух местах после прошлой итерации — теперь убираем.
Controller:
- SovietBlockchainPort.getPrograms(coopname) + реализация в адаптере.
- AgreementService.getCooperativePrograms(coopname).
- Query cooperativePrograms (без auth-guard — публичный конфиг кооператива).
- DTO CooperativeProgramDTO {id, coopname, program_type, is_active, draft_id}.
SDK:
- regen schema.gql + zeus.
- Selector cooperativeProgramSelector.
- Query Queries.Agreements.CooperativePrograms.
Desktop:
- entities/Wallet/api: loadUserProgramWalletsData теперь дёргает
client.Query(CooperativePrograms) вместо fetchTable.
- entities/Wallet/model: ICoopProgramData (полная chain-запись) заменён
на минимальный ICoopProgramSummary {id, title, program_type, is_active?,
draft_id?} — UI использует только эти поля + title из cooptypes registry.
- extensions/reports/.../participant-wallets-api.ts: то же самое для
таблицы «Пайщики» в Реестре кошельков.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Стол совета:
- Удалён старый /soviet/ledger ("Реестр кошельков"). Новый реестр живёт
в Отчёты ФНС → /reports/wallets (entities/Ledger2 + GraphQL).
- Снесены сопутствующие файлы (pages/Cooperative/ListOfLedgerAccounts,
widgets/LedgerAccounts, entities/LedgerAccount) — внешних потребителей нет.
Реестр программ ЦПП (cooptypes/src/ledger2/programs.ts):
- TS-only массив LEDGER2_PROGRAMS с короткими (`short_label`) и полными
(`display_name`) метками: ЦПП Цифровой кошелёк, ЦПП Маркетплейс,
ЦПП Генератор, ЦПП Благорост.
- helper getProgramLabel(id) / getProgramShortLabel(id) с fallback на
`Программа №<id>`. Менять централизованно тут, не в чейн-таблицах.
Применение в UI:
- ParticipantWalletsPage.vue: заголовок колонки программы = display_name
из реестра (а не chain-поле title).
- entities/Wallet/api: program_details.title в loadUserProgramWalletsData
подменяется на display_name из реестра.
Цвета (theme-aware):
- Хардкод #f5f6f8/#e0e0e0/#222/#ccc/#2e7d32/#8d6e63/#bbb в
ParticipantWalletsPage заменён на var(--q-positive)/--q-warning и
rgba с body--dark overrides.
- text-grey-6/7 заменён на свой класс caption-muted (rgba + body--dark)
в Coop/Participant/Transfer pages — quasar text-grey-X не реагирует на
body--dark и плохо читается на тёмной теме.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Симптом: при «Подтвердить платёж» в реестре платежей bundle регистрации
(createaccount + reguser + completeincome + signagree) падал с
"Пайщик не найден в кооперативе" — wallet::signagree вызывал
get_participant_or_fail, а в этом bundle participant ещё не создан
(создаётся позже в soviet::confirmreg → soviet::addpartcpnt после
голосования совета).
Решение: симметрично с soviet::sndagreement убираем проверку participant.
Доверие — на require_auth(coopname) + get_cooperative_or_fail +
verify_document_or_fail + get_program_or_fail. Точно та же модель доверия,
что у sndagreement.
Дополнительно: пишем подписанный документ в реестр документов через
Soviet::make_complete_document — чтобы программные соглашения отображались
в общем off-chain реестре документов кооператива (как непрограммные через
sndagreement). Добавлена константа Names::WalletActions::SIGN_AGREEMENT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AccountBlockchainAdapter.registerBlockchainAccount пушил все agreements как
soviet::sndagreement в одном tx — после гейта Эпика 2 такие actions с
program_id > 0 контракт активно отвергает, и вся регистрация (createaccount +
registeruser + completeincome + agreements) откатывалась с
"программные соглашения подписываются через wallet::signagree".
Симптом: «Подтвердить платёж» в реестре платежей роняет всю транзакцию
регистрации.
Решение: один lookup soviet::coagreements на bundle, формируем map
agreement_type → ICoopAgreement, затем для каждого agreement из конфига:
- program_id == 0 → soviet::sndagreement (как раньше);
- program_id > 0 → wallet::signagree с program_id/draft_id из coagreement.
wallet::signagree требует существующего пайщика (get_participant_or_fail).
В bundle регистрации это гарантировано предыдущим registeruser action в той
же транзакции — actions исполняются последовательно.
Логика теперь идентична AgreementInteractor.sendAgreement (один источник
правды для роутинга).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Убираем прямые fetchTable из desktop entities/Agreement (ADR: фронт не
ходит в чейн напрямую — всё через controller). Это последний шаг для
корректной работы виджета RequireAgreements: вместе с фиксом type-поля
программных DTO (предыдущий коммит) фронт теперь видит подписанные
соглашения и не показывает повторный диалог.
Контроллер:
- AgreementService: getCoopAgreements (через SovietBlockchainPort.getCoagreements)
и getAgreementTemplates (через BLOCKCHAIN_PORT.getAllRows для draft::drafts —
глобальные scope=draft + per-coop, объединение).
- AgreementResolver: 2 новые Query — `cooperativeAgreements(coopname)` и
`agreementTemplates(coopname)`. Без auth-guard'ов: данные публичные
(конфиг кооператива и шаблоны документов).
- 2 DTO: CoopAgreementDTO, AgreementTemplateDTO.
SDK (regen schema + zeus + новые selectors/queries):
- Queries.Agreements.CooperativeAgreements
- Queries.Agreements.AgreementTemplates
Desktop:
- entities/Agreement/api/index.ts: fetchTable → client.Query.
Контракты, фронтовый виджет, остальные fetchTable не трогали.
Раньше programmatic-DTO синтезировался с заглушкой type='programmatic'.
Виджет RequireAgreements на фронте матчит подписи через
userAgreement.type === coagreement.type ('wallet'/'blagorost'/...) — заглушка
никогда не совпадала, и пайщик после подписания снова видел диалог
«подпишите соглашение». Каждое нажатие 'Подписать' успешно делало upsert
через wallet::signagree (одна и та же программа, обновляются version/draft_id/
signed_at), но визуально ничего не менялось.
AgreementService теперь:
- Подгружает soviet::coagreements кооператива один раз на запрос (≤10 строк).
- Строит map program_id → type.
- programAgreementToDTO ставит type из map'а; fallback 'programmatic' только
если коагримент по этой программе не настроен (теоретический случай).
SovietBlockchainPort.getCoagreements (множественное) выделено из существующего
getCoagreement; getCoagreement теперь его потребитель.
Контракты, GraphQL-схема, фронт — без изменений.
После Эпика 3 ledger2::userwallets хранит только non-zero записи (контракт
стирает row при достижении нуля), и подписавший соглашение пайщик без
переводов не видел свой кошелёк — UX-регрессия по сравнению с legacy
soviet::progwallets.
Теперь WalletInteractor.assembleProgramWallets:
1. Берёт ожидаемые (username, program_id) из wallet::users.programs[] через
USER_AGREEMENT_REPOSITORY.
2. Объединяет с L3-rows из ledger2::userwallets как раньше.
3. Для каждой пары без L3-row создаёт stub с нулями в валюте кооператива.
4. Сэмпл asset-а для zeroAssetLike берётся из существующего L3-row либо из
coop.initial через BLOCKCHAIN_PORT.getCooperative; fallback '0.0000 RUB'.
5. block_num и timestamps stub'а — из programs[].signed_at (детерминированно).
Split ЦК (w.wal.share + w.wal.member) сворачивается в один entity как раньше.
Marketplace (program_id=2) кошельков не имеет, фильтруется через cooptypes
Ledger2.walletNamesForProgram(pid).length === 0.
Контракты, GraphQL-схема, фронт — без изменений.
Раньше диалог делал прямой `transact` → `soviet::sndagreement` от имени пайщика.
После Эпика 2 программные соглашения (program_id > 0) пишет `wallet::signagree`,
требующий `coopname@active` — пайщик не может подписать такую транзакцию из
браузера, контракт ожидаемо отвергал.
Теперь идёт через GraphQL-мутацию `sendAgreement` (тот же путь, что у капитал-
extension через `useSendAgreement`). Контроллер читает `coagreement.program_id`
и сам выбирает action: `wallet::signagree` для program_id > 0, `soviet::sndagreement`
для непрограммных. Подписи пайщика на документе сохраняются и проверяются
контрактом.
Заодно убрал лишний JSON.stringify(meta) — GraphQL принимает meta объектом,
сериализацию делает контроллер при сборке chain-action.
`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.
После Эпика 2 soviet::sndagreement отвергает программные соглашения
(program_id > 0), а фронт зовёт его одинаково для всех типов. Контроллер
теперь читает coagreements кооператива и для program_id > 0 уходит в
wallet::signagree (coopname@active), оставляя soviet::sndagreement только
для непрограммных. Фронт менять не нужно.
В первоначальной версии 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>
Валидатор PaginationUtils ограничивает limit ≤ 1000; getAgreements падал
с HttpApiError при загрузке wallet.agreements в desktop. Заменил один
запрос с limit=10000 на цикл по страницам.
Обнаружено визуальной проверкой через docs-harness auth/signin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавлены интеграционные тесты на live-блокчейне:
- wallet::signagree повторно с тем же program_id → обновляет doc_hash без дубля.
- wallet::revokeagree без записи users → throws.
- wallet::revokeagree program_id не в programs[] → throws.
- ledger2::migrate3 идемпотентность (двойной вызов не дублирует).
- ledger2::migrate3 с blocked > 0 → blocked сохраняется отдельно.
- ledger2::migrate3 (0,0) когда записи нет → no-op.
Каждый тест содержит свой setup/cleanup (ensureNoProgram/ensureNoUserWallet),
чтобы порядок запуска не влиял на корректность.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- entities/Wallet: loadUserAgreements/loadUserProgramWalletsData → GraphQL
(читают объединённый источник из соответствующих резолверов).
- entities/Agreement: убраны мёртвые loadAgreementsOfAllParticipants и
loadSingleUserProgramWalletData; store сужен до фактически используемых полей.
- WalletsPage: participant-wallets-api тянет программные кошельки через GraphQL.
- RequireAgreements: сравнение версий через Number() (template.version из BC —
string, userAgreement.version из DTO — number).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AgreementService.getAgreements объединяет два источника (Эпик 2):
- непрограммные (program_id == 0) из soviet::agreements3 через AgreementRepository
- программные из wallet::users.programs[] через UserAgreementRepository
Программные соглашения разворачиваются в плоский ряд AgreementDTO
(synthetic id отсутствует, document лежит в action data, status=CONFIRMED).
WalletInteractor.getProgramWallets/getProgramWalletsPaginated читают из
ledger2::userwallets (UserWalletRepository) и агрегируют split-кошельки:
для program_id=1 (ЦК) собирает w.wal.share + w.wal.member в один
ProgramWalletDTO с membership_contribution из w.wal.member.
PROGRAM_ID_TO_WALLET_NAMES в domain/wallet/utils синхронизирован с
LEDGER2_USER_SHARED_PROGRAM_MAPPING в C++ контрактах и migrator/049.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UserAgreement (Эпик 2 / wallet::users): доменная сущность owner'а
программных соглашений с массивом programs[] (jsonb), full sync-цепочка
поверх AbstractEntitySyncService — coopname берётся из delta.scope.
UserWallet (Эпик 3 / ledger2::userwallets): доменная сущность L3-учёта
по USER_SHARED-кошелькам, sync с уникальным id и индексом
(coopname, wallet_name, username).
Контракт-info сервисы wallet/ledger2 регистрируются параллельно
SovietContractInfoService; новые репозитории и sync-сервисы подключаются
в TypeOrmModule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Контрактный assert L3 ⊆ L2 USER_SHARED — последняя линия защиты инварианта (NFR2).
- LEDGER2_USER_SHARED_PROGRAM_MAPPING: маппинг wallet_name → required_program_id
(хардкод-таблица рядом с LEDGER2_WALLET_REGISTRY; ADR-004)
- w.reg.minshr → 0 (исключение, без проверки соглашения)
- w.wal.share / w.wal.member → 1 (ЦК)
- w.cap.blago → 4 (Благорост)
- w.cap.gen → 3 (Генератор)
- ledger2_required_program_id: helper с runtime check на отсутствие маппинга
для USER_SHARED-кошелька (защита от пропуска при добавлении нового кошелька)
walletop:
- Pre-flight cross-contract READ wallet::users[username].programs[] для
USER_SHARED-сторон (исключение w.reg.minshr через required_program_id == 0);
fail-fast до мутаций
- Post-mutation Σ L3.{available,blocked} == L2.{available,blocked} на каждом
затронутом USER_SHARED-кошельке (O(N_users), но это критичный assert)
apply:
- username обязателен для USER_SHARED на нормальном пути (НЕ-миграционные
operation_code); исключение — o.mig.*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
migrate3 — идемпотентная per-record миграция L3-балансов:
- Заполняет userwallets[coopname][wallet_name, username] (overwrite, не +=)
- Без бух-проводок (это инициализация state, не операция)
- Без cross-contract check wallet::users.programs[]
(миграция допускается ДО переезда соглашений; rollout-окно)
- Auto-delete при (available=0, blocked=0)
- Только USER_SHARED-кошельки (compile-time check на runtime)
- Auth coopname@active, payer coopname (NFR11)
L3-зеркало в revert — без изменений семантики: revert уже принимает
username и пробрасывает в walletop, который для USER_SHARED применяет
mirror op к L3-записи того же пайщика. Эпик 3 / story 3.3 покрывается
существующим путём.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Третий уровень учёта (L3) — пользовательские балансы на USER_SHARED-кошельках.
- Новая таблица userwallets {wallet_name, username, available, blocked}
scope=coopname, payer=ledger2 (TODO D2 → coopname через linkauth)
- Composite key by_userwallet (combine_ids), индексы byuser/bywallet
- walletop расширен параметром username; передаётся из apply/walmove/revert
- USER_SHARED-сторона + username → upsert/delete L3 запись
- COOPERATIVE-сторона → username игнорируется
- Пустой username на USER_SHARED → L2-only mode (для совместимости со старым
ledger2::migrate, который агрегирует без разбивки по пайщикам;
полноценное заполнение L3 — через ledger2::migrate3 per-record, story 3.3)
- Auto-create L3 при первом ISSUE/TRANSFER/UNBLOCK
- Auto-delete при обнулении (available + blocked == 0)
НЕ входит в story 3.1 (вынесено в 3.2):
- cross-contract check wallet::users.programs[]
- post-mutation assert Σ L3 == L2
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Контракт wallet — owner программных соглашений (ADR-008).
- Новая таблица users {username, programs[]} (scope=coopname, payer=coopname)
- program_agreement: program_id, doc_hash, version, draft_id, signed_at
- Документ соглашения НЕ в state — только хэш и метаданные;
полный текст лежит в action data signagree (audit trail)
- signagree: upsert по (username, program_id); auth coopname@active;
проверки кооператива/пайщика/программы/draft_id
- revokeagree: удаление программы из vector; пустой vector → erase users;
auth coopname@active
- migrate3: идемпотентная per-record миграция из soviet::agreements3
без проверок программы/документа (заполняет state как есть);
auth coopname@active
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Тесты с processDecision (walmove.test.ts → registerUser, capital.test.ts через
investInProject/withdrawContribution/registerExpense, registrator.test.ts)
прогоняются на стенде после `pnpm run reboot:extra`, который создаёт
расширенный совет из 5 человек: ant + petr + anna + mikhail + olga
(см. infra.ts § installInitialData с isExtended=true).
Soviet-контракт требует консенсус по большинству — 3+ голоса из 5. Старая
реализация processDecision голосовала только от ant (1 голос), и контракт
отвечал «Консенсус совета по решению не достигнут» в soviet::authorize.
Голосуем тремя (ant chairman + 2 member): минимум для прохождения. Все
члены используют один и тот же default_public_key (см. infra.ts:407 —
changeKey всем установлен config.default_public_key), поэтому подпись
chairman-WIF удовлетворяет authorization для всех трёх actor.
Проверено на стенде:
• walmove.test.ts 3/3 ✓ (было 2/3 — фейл AC1)
• ledger2-wallets-registry.test.ts 14/14 ✓
• ledger2-migrate.test.ts 10/10 ✓
• ledger2-read-layer.test.ts 5/5 ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
standards-site публикуется на gh-pages вместе с остальной докой. Чтобы
изменения стандартов из reports/marketplace2 попадали в превью на
docs.coopenomics.world/standards/, добавляем эти ветки в push-триггер.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В dev эти 4 standards-yaml заморожены 23 апреля. В reports они с 24-го
переименованы и переписаны (префиксы o./p., стиль C, шлифовки). Удаляем
их в dev, чтобы при мердже dev → reports пришли актуальные версии без
конфликтов. reg.adduser/reg.recovery в reports пока нет — будут заведены
заново под новую конвенцию по необходимости.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Полный fail-fast ломал dev-сборку bootcoop: ledger2 живёт пока в ветке
reports, в dev закомментирован в components/contracts/CMakeLists.txt
и не попадает в dicoop/contracts:dev. boot:remote падал на ENOENT
ledger2.wasm, хотя в dev контракт временно не нужен.
Компромисс:
- ENOENT (отсутствующий wasm/abi) — console.warn + return.
Когда ветка reports мержится в dev → CMakeLists раскомментирует ledger2
→ dicoop/contracts:dev его подтянет → bootcoop установит автоматически,
без правок в boot. No merge conflicts.
- Все остальные ошибки (RPC, transaction reject, abi parse) — re-throw
обогащённой Error: имя контракта, target, путь, cause. Это поведение
для production-relevant сбоев остаётся как в предыдущем коммите.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше Blockchain.setContract молча проглатывал ENOENT (отсутствующий
wasm/abi) и любые ошибки api.transact: console.log + return. boot:remote
завершался exit 0 даже когда часть контрактов не установилась —
sideboot мог отдать "готовую" цепь с дырами в развёртывании.
Изменения:
- await на api.transact (раньше fire-and-forget — ошибки транзакций
тоже не ловились, только синхронные fs.readFileSync)
- catch перебрасывает обогащённой Error: имя контракта, target-аккаунт,
путь к артефакту, причина (через cause). startInfra прерывается
в startInfra → boot:remote → exit 1.
SIDEBOOT.md: dicoop/blockchain_v5.1.1:dev → dicoop/blockchain:latest
во всех ссылках (таблица артефактов + compose snippet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Краткий quickstart для AI-агентов и людей, которым нужна локальная
EOSIO-нода + контракты в стороннем репозитории (parser2,
blockchain-protocol) для интеграционных тестов, без mono-инфры.
Содержит:
- список артефактов в hub (blockchain_v5.1.1, contracts, bootcoop) и
правила тегов
- минимальный docker-compose snippet (~30 строк)
- источник конфигов ноды (components/boot/src/configs/)
- команды запуска и чистого перезапуска (down -v + rm blockchain-data)
- проверка готовности через get_code на всех ожидаемых аккаунтах
- опциональные mono-data флаги (INSTALL_*_DATA)
- куда смотреть в коде если что-то не работает
- известные ограничения (boot:remote не падает на missing wasm,
dicoop/contracts ребилдится только при изменении контрактов)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ERESOLVE падал на @typescript-eslint/parser@^7.8.0 (cooptypes.deps)
требующем peer eslint@^8, а у boot.devDeps eslint@^9.
Cooptypes — type-only пакет, его одна prod-dep (@typescript-eslint/parser)
в runtime не нужна. Убираю мердж cooptypes.deps. Только factory.deps
мерджу в boot.dependencies.
Дополнительно --legacy-peer-deps на npm install — на случай других
peer-конфликтов в transitive graph factory'а.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline workspace-deps cooptypes/factory тянет в bundle их транзитивные
npm-зависимости (factory: handlebars, ajv, mongodb, nunjucks, pdf-lib,
moment-timezone, uuid, json-schema, inline-css). unbuild warning'ит
эти как "implicit external" и при failOnWarn:true роняет билд.
Фикс:
- build.config: failOnWarn:false — warnings допустимы
- Dockerfile: после build мерджим factory.deps + cooptypes.deps в
boot/package.json. Boot.deps побеждают в случае коллизии. Так
npm install --omit=dev в runtime поставит всё что транзитивно
нужно для inlined factory кода.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Полный bundle (inlineDependencies:true) упал на native binding
cpufeatures.node — dockerode тянет ssh2 → cpu-features → нативный
.node файл, который rollup не может включить в bundle.
Компромисс:
- workspace-deps cooptypes/factory bundle'ятся inline через rollup hook
(через external override) — иначе они теряются при npm install
(workspace:* specifier'ы в package.json)
- npm-deps остаются external — нативные binaries сохраняются как есть
- runtime: npm install --omit=dev ставит только prod npm-deps
Финальный образ ~270MB:
- node:22-slim ~75MB
- dist (boot bundle с inlined workspace-deps) ~250K
- node_modules только prod npm-deps
- /contracts ~3MB
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
unbuild 2.0.0 имеет баг: options.externals.push(...pkg.dependencies)
выполняется ДО проверки inlineDependencies, поэтому inlineDependencies:true
de-facto не работает — всё из package.json остаётся external. Bundle
получался ~200K с require('commander'), require('pg') и т.д., и в
финальном образе всё падало.
Обход: hook 'rollup:options' переопределяет external напрямую, делая
external только Node built-ins. Всё остальное inline'ится в bundle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Прошлая попытка (inlineDependencies массивом строк) не сработала —
unbuild 2.0.0 принимает только boolean. После build factory всё ещё
require'ился из node_modules, и образ падал.
Переключил на inlineDependencies:true — bundling ВСЕХ deps (workspace
+ npm) внутрь одного dist/index.cjs. В финальной runtime-стадии больше
нет npm install и node_modules — только COPY одного файла bundle и
COPY /contracts.
Ожидаемый размер ~100-150MB (node:22-slim ~75MB + bundle).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Прошлый образ занимал 1.5GB на диск из-за pnpm storage в /deploy/node_modules/.pnpm
(deploy --legacy не отфильтровал devDependencies).
Двухсторонний фикс:
1. boot/build.config.ts: добавил rollup.inlineDependencies для cooptypes
и @coopenomics/factory — workspace-deps теперь bundle'ятся прямо в
dist/index.cjs, в node_modules финального образа их не нужно.
2. boot/Dockerfile: заменил pnpm deploy на classic npm install --omit=dev
в runtime-стадии. В builder перед копированием стираем cooptypes/factory
из package.json (они уже в bundle), npm зову без --frozen-lockfile —
pnpm-lock.yaml для npm бесполезен. Финальный образ содержит:
- dist/index.cjs (boot bundle ~200K с inlined workspace-deps)
- node_modules только prod npm-deps (mongoose/pg/eosjs/...)
- /contracts (~3MB)
Ожидаемый размер ~250MB на диск против 1.5GB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smoke-test pulled dicoop/bootcoop:dev упал на "Cannot find module
'@coopenomics/factory/dist/index.cjs'". Причина: pnpm deploy --prod
копирует node_modules в /deploy через симлинк-разрешение, а у
workspace-зависимостей factory/cooptypes ссылка идёт на dist/, который
не был собран — звался только pnpm build для самого boot.
Замена: pnpm --filter "@coopenomics/boot..." build — троеточие тащит
транзитивные workspace-deps (cooptypes, factory), у обоих unbuild
конфиги, dist/ генерится корректно.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Предыдущая версия тащила весь workspace в финальный образ (~1.5GB).
Перепилил в три стадии:
1. builder — full workspace + pnpm install + unbuild build → dist/
+ pnpm deploy --prod --legacy /deploy (self-contained)
2. contracts — dicoop/contracts:<tag> alias
3. runtime — node:22-slim + COPY /deploy + COPY /contracts
В финальном образе остаётся только:
- dist/index.cjs (unbuild-бандл boot)
- node_modules только prod-зависимостей (без dev)
- /contracts (~40MB)
ENTRYPOINT прямо вызывает node dist/index.cjs — без pnpm/esno-обёртки,
быстрее старт, меньше зависимостей.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dicoop/mono-base тегается только под semver-релизы (build-containers.yaml
триггерится на refs/tags/*), поэтому :dev/:testnet/:main отсутствуют —
bootstrap workflow упал на pull этого тега.
Перепилил Dockerfile: COPY весь workspace из текущего checkout'а +
pnpm install --filter "@coopenomics/boot..." (тащит boot + транзитивные
cooptypes/factory). .dockerignore отрезает node_modules/dist/blockchain-data.
Workflow упростился — pull только dicoop/contracts, MONO_BASE_TAG больше
не нужен.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Цель: в соседних репозиториях (parser2, blockchain-protocol и т.д.)
для тестов и отладки нужно поднимать только цепь + контракты, без
mono-инфры (mongo/postgres/desktop). Решение — два контейнера в их
compose: dicoop/blockchain (нода) + dicoop/bootcoop (one-shot bootstrap).
Что добавлено:
- components/boot/Dockerfile — multi-stage от dicoop/contracts:<tag>
(контракты flat-layout в /contracts) + dicoop/mono-base:<tag>
(runtime с pnpm/node + workspace). ENTRYPOINT = pnpm run boot:remote.
CONTRACTS_DIR=/contracts выставлен по умолчанию.
- .github/workflows/build-bootstrap.yaml — push в dev/testnet/main
билдит и пушит dicoop/bootcoop:<branch> + :latest для main +
:<branch>-<short-sha> для пинов. Telegram нотификации.
- components/boot/README.md — раздел про sideboot с готовым compose
snippet'ом, переменными окружения и опциональными INSTALL_*_DATA.
Версионирование dicoop/bootcoop:<tag> совпадает с dicoop/contracts:<tag>
(multi-stage из того же тега) — контракты и bootstrap синхронны.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enforcement-выжимка из ARCH: Синхронизация узла с блокчейном v1 +
ARCH: Интеграция контроллера с parser2 v1 (оба в _blago/13/components/14).
~80 правил в 14 секциях: composite-entity namespaced (db/bc/derived),
dispatch pipeline (dedup → save → wake → emit), write-mutation через
pool.submitWithPool + waitForDelta + subscription, read-path = PG only,
fork через ForkRegistry sequential, pool state machine с placeholder
pattern + OrphanPendingReconciler, anti-patterns.
Claude Code автоподхват при работе в controller/. Source of truth —
ARCH-документы в blago; этот файл — только enforcement layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Теперь все production-сервисы запускаются изнутри dicoop/mono-base
без volume-mount и без devDeps:
- @coopenomics/parser: scripts.start `esno src/index.ts`
→ `node ./dist/index.cjs` (unbuild уже собирал dist).
- @coopenomics/notifications: scripts.sync `tsx src/sync/sync-runner.ts`
→ `node ./dist/sync/sync-runner.cjs`. Добавлен entry
`src/sync/sync-runner` в build.config.ts — unbuild делает
bundle с разрешёнными импортами (старый tsc-output ломался
под чистым node из-за extension-less ESM-импортов).
Поле `bin.novu-sync` обновлено на `.cjs`.
- @coopenomics/controller: `tsconfig-paths` перенесён из devDeps
в deps. Сервис стартует через `ts-node -r tsconfig-paths/register`
(ts-node уже был в deps). После prune --prod оба останутся.
В корневом Dockerfile вернули `pnpm prune --prod --ignore-scripts`
после lerna run build — выкидывает все devDeps (typescript, vitest,
unbuild, eslint, quasar, tsx, esno и пр.).
Smoke-test slim-образа `dicoop/mono-base:dev-local` (2.96GB):
- parser → дотягивает до runtime, валится на missing NODE_ENV ✓
- notifications → стартует, валится на missing NOVU_API_KEY ✓
- desktop → дотягивает до runtime, валится на missing chain url ✓
- boot --help → работает ✓
- coopback (controller) → ts-node компилирует, стартует ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cooparser стартует через `esno src/index.ts` (esno = devDep) и
notifications — через `tsx src/sync/sync-runner.ts` (tsx = devDep).
В playbook'е monocoop pull-based деплой (docker-compose.containers.yaml)
запускает их без volume-mount, изнутри образа. После prune --prod
оба упали бы на старте с `esno: not found` / `tsx: not found`.
mono-base остаётся ~5GB (вместо 3GB после prune). Полное сжатие
вернём отдельным шагом — после того как cooparser и notifications
переведём на `node dist/...`.
Multi-stage сама по себе уже даёт win: 7.36GB → ~5GB через
.dockerignore + apt --no-install-recommends + python3-only-runtime
без gcc/dev-headers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Корневой Dockerfile переписан под две стадии: builder с full toolchain
(apt dev-headers, WeasyPrint в venv, pnpm install + lerna run build +
pnpm prune --prod --ignore-scripts) и runtime — slim node:22-slim
без gcc/dev-headers, только runtime-libs WeasyPrint + готовый /app.
Локально сжалось с 7.36GB до 2.95GB (-60%). Образы наследники
(dicoop/desktop / coopback / cooparser / notificator / notifications)
автоматом получат тонкий runtime через build-containers.yaml.
Безопасность для playbook'ов monocoop: их docker-compose.yaml собирает
сервисы из components/{controller,parser,desktop}/Dockerfile (не
корневого) и монтирует ./:/app — корневой mono-base туда вообще не
попадает.
.dockerignore — выкинул .git (962MB), blockchain-data/wallet-data,
_blago, coverage, *.log и т.п. Build-context теперь намного легче.
docker-compose.testnet.yml — локальный 3-сервисный smoke-стек:
ke-node (dicoop/blockchain_v5.1.1:dev) + ke-contracts (one-shot
copy из dicoop/contracts:dev в shared volume) + ke-bootstrap
(mono-base, node /app/components/boot/dist/index.cjs boot:remote).
Изолирован от dev-ноды mono-ai-4-node-1 через отдельную сеть и порты
8919/9907/8101.
В playbook'ах не применяется (там docker-compose.yaml, не .testnet.yml).
.gitignore — добавлен .env.testnet (содержит EOSIO_*KEY).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Boot — это и есть бутстрап. Дубликат через отдельный образ создаёт
второй workflow и две точки сборки там, где для testnet/prod достаточно
запустить existing pnpm -F @coopenomics/boot run boot:remote внутри
dicoop/mono-base:<tag>, который уже собирается через build-containers.yaml.
Что остаётся в dev (полезное из предыдущих коммитов):
- boot:remote команда — без dockerode, для сценария когда нода
поднята отдельным сервисом docker-compose.
- CONTRACTS_DIR env-driven path в configs/contracts.ts — позволяет
монтировать dicoop/contracts:<tag> как volume.
- "build" script в boot/package.json для unbuild.
Удалено:
- components/boot/docker/ (Dockerfile + entrypoint + bootstrap.package.json
+ bootstrap.pnpm-workspace.yaml)
- .github/workflows/build-bootstrap.yaml
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Симметрия с dicoop/contracts и остальными dicoop/* образами;
GHCR делает first-publish приватным и требует ручного visibility-toggle.
DockerHub-секреты DOCKERHUB_USERNAME / DOCKERHUB_TOKEN уже используются
build-contracts.yaml и build-containers.yaml.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Без явного `files` поля `pnpm deploy --prod` не копирует dist/ в /app
(unbuild создаёт его, но pnpm считает не-входящим в публикуемый
артефакт). Из-за этого ENTRYPOINT bootstrap-образа не находил
/app/dist/index.cjs на стадии Verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Корневой /package.json ссылается на 14 workspace-пакетов (cleos,
controller, desktop, ...), которые в build-context bootstrap-образа
не копируются — pnpm install падает с ERR_PNPM_WORKSPACE_PKG_NOT_FOUND.
Решение: положить рядом с Dockerfile минимальные bootstrap.package.json
и bootstrap.pnpm-workspace.yaml, в которых workspace = только
[boot, cooptypes, factory]. pnpm install идёт с --no-frozen-lockfile
(локфайл генерится прямо в build-stage, drift безопасен — финальный
образ запинен через branch+sha теги).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BuildKit делает ARG, объявленный после FROM, локальным для предыдущей
стадии — поэтому FROM dicoop/contracts:${CONTRACTS_TAG} ругался
'invalid reference format'. Глобальный ARG до всех FROM фиксит.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Образ ghcr.io/coopenomics/bootstrap для one-shot первичного деплоя
контрактов на KE-узел в docker-compose. Соответствует решению из
project_ke_bootstrap_strategy: boot/contracts собираются в mono,
apps-catalog только потребляет готовые образы.
Состав:
- components/boot/src/index.ts — команда `boot:remote`: ждёт RPC
по CHAIN_URL, вызывает startInfra() через eosjs (без dockerode).
Опциональные initial-data/extra-data через env-флаги.
- components/boot/src/configs/contracts.ts — env-driven CONTRACTS_DIR.
Когда задан — flat layout /contracts/<name>/<name>.{wasm,abi}
(как в dicoop/contracts), иначе старые относительные пути.
- components/boot/package.json — script `build` (unbuild) +
`boot:remote` для локального запуска.
- components/boot/docker/Dockerfile — multi-stage:
deps → build (cooptypes/factory/boot) → pnpm deploy --prod →
COPY --from=dicoop/contracts:<tag> → node:20-alpine slim runtime.
- components/boot/docker/entrypoint.sh — режимы boot:remote / verify
/ shell + проверка обязательных env'ов.
- .github/workflows/build-bootstrap.yaml — публикация в GHCR по push
в dev/testnet/main + verify smoke-test после push'а.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E402 при первой публикации @coopenomics/inter — npm требует
явный access:public в package.json (lerna-глобальный access
не действует на первый scoped-publish).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Контракт apps (каталог приложений ВОСХОД) теперь:
- разворачивается через mono boot (account apps + setContract из
build/contracts/apps);
- имеет TS-обёртку в cooptypes — Actions/Tables/Interfaces по
паттерну остальных контрактов, AppsContract в общем индексе.
interfaces/apps.ts написан вручную, потому что eosio-abi2ts 1.2.2
не парсит optional-типы (name?, checksum256?) из action setcoop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Контракт `apps` (координационная плоскость для apps-catalog):
- 4 таблицы: packages, releases, subs, coops; ABI 21 KB, WASM 70 KB
- 11 actions: regpackage, transferpkg, setrelease, reactivate, withdraw,
cleanup, regsub, expsub, regcoop, setcoop, migrate
- subs.chain_id и coops.chain_id — каноническая идентификация подсети
- coops.signing_key — отдельный subnet-signing-key (не active),
ротация через setcoop без потери ранее выпущенных JWT
- releases с TTL retention 90 дней + inline cleanup до 50 записей
- atomic supersede в setrelease — выполняет FR8 «approve → ACTIVE»
на стороне blockchain'а
CI контейнер `dicoop/contracts`:
- упаковывает все 21 контракт (15 user + 6 system eosio.*) в alpine 10.8 MB
- список контрактов берётся из CMakeLists (single source of truth)
- триггер push на dev/testnet/main + workflow_dispatch
- маппинг ветка→tag: dev/testnet/main + sha-pinned tag для воспроизводимости
- manifest.json с sha256 каждого артефакта внутри образа
- entrypoint: copy [name] | manifest | sha256 | list | ls
- multi-stage потребление в ke-bootstrap = 0 MB на VPS
Закрывает блокер Story 6.x (KE bootstrap) для apps-catalog.
serializeBlagoMarkdown звал matter.stringify(body, data) — gray-matter сначала парсит body как frontmatter; story с описанием, начинающимся с «---», ловилась как фронтматтер и валила js-yaml на строках с двоеточием (`Authorization: Bearer <token>`). Теперь сериализуем заголовок отдельно (matter.stringify('', data)) и сами клеим body; подрезаем лишний хвостовой `\n`, чтобы etag оставался стабильным между pull'ами.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Заменил компьютацию (compute_min_total_by_type, sum_progwallet_available,
sum_progwallet_blocked, чтение legacy_861) на 5 const-констант с суммами,
которые правятся прямо в файле при необходимости. Удалил sum_progwallet_available
— больше не нужна.
Бух-баланс таблицы: Dr 51 = 614 200, Cr 80 = 601 700, Cr 86 = 12 500;
Σ Dr = Σ Cr ✓.
Прочие кооперативы — без изменений (по-прежнему через share_money / rid_share
+ compute_min_total_by_type).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Расчёт минимального паевого учитывает тип: organization → coop.org_minimum,
иначе → coop.minimum (фолбэк на minimum, если org_minimum/type не выставлены).
Кэш active_participants_count больше не используется — он не несёт разбивку
по типу.
Для voskhod добавлена отдельная ветка миграции по фактическим суммам
progwallets (legacy::accounts и progwallets рассинхронизированы): Σ pid=1
→ w.wal.share, Σ pid=4 → w.cap.bginv, Σ pid=3 → w.cap.gncom; вступительные
из legacy_861, мин. паевые расчётно. TRANSIT_RID для voskhod не шлём —
имущество остаётся осадком на legacy 80 до Phase 2 (ADR-009).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В PaymentDomainEntity добавлен транзиентный isNewlyCreated — true когда
платёж создан вот сейчас, false когда поднят существующий PENDING через
findActivePendingPayment. RegistrationService шлёт sendNewInitialPayment
только при isNewlyCreated=true, поэтому повторные мутации фронта (reload,
immediate-watch на step) больше не плодят 7 одинаковых сообщений
председателю. Поле не пишется в БД и не уходит в DTO.
ActiveUserStatusGuard точечно на мутации createDepositPayment — пайщик до
приёма советом (status≠active) получает 403 вместо ушедшей в обработку
заявки. На фронте сама кнопка «Совершить взнос» скрыта в DepositButton
по тому же признаку — не водим пользователя по тупиковому пути.
Предыдущий обход (после `setStatusActive` уходить в Мастерскую и
кликать компонент из общего списка) был оправдан только тем, что
вкладка «Компоненты» проекта отдавала «Нет компонентов». Корень оказался
не в clearance — controller компоненты не фильтрует, доступ на чтение
открыт всем. Это race с парсером: после `capital::startproject`
mongo-parser ещё не успевал обработать дельту проекта, и
`capitalProject`-resolver возвращал `components: []`. Через 3-5 секунд
тот же запрос отдавал список как надо.
Теперь в шаге 11:
1. После `setStatusActive` ждём 3с — даём парсеру довести startproject.
2. Кликаем вкладку «Компоненты».
3. Ждём до 30с появления компонента в списке (строгий waitFor).
4. Только потом кликаем по нему.
Скриншот 06-component-page-active обновился — это тот же экран, что
снимался обходным путём, разница только в навигации.
UI Capital extension к этому моменту переехал: «Адаптация к работе с
программой "Благорост"» теперь живёт в CapitalOnboardingCard для
председателя, а пайщику показывается мастер регистрации
(CapitalRegistrationPage). Старый сценарий искал заголовок
«Адаптация» на странице регистрации — там его уже нет, председательский
путь падал, пайщиковый и не предполагался.
Разделил на два сценария:
1. **adaptation** (пайщик newadapter) — мастер регистрации:
01-roles → 02-time-resource → 03-rate → 04-about → 05-documents.
Используется новая фикстура newadapter (Сидоров А. М.), которая
намеренно остаётся БЕЗ Capital-регистрации (фаза 05 не запускается),
чтобы UI открыл ей мастер. Подписание документов в самом сценарии не
делается — иначе при повторном прогоне мастер не покажется.
adaptation.md полностью переписан.
2. **adaptation-chairman** (новая страница) — карточка для председателя
с пятью шагами и кнопками «Объявить собрание совета» + диалог
«Предложение повестки». Кадры: 01-overview, 02-propose-dialog.
Содержание adaptation-chairman.md описывает приём советом пяти
документов программ ГЕНЕРАТОР и БЛАГОРОСТ.
Поддержка инфраструктуры:
- shoot.mjs: добавлена фикстура newadapter (Сидоров А. М.) в
KNOWN_FIXTURES — фабричный пайщик без Capital-регистрации.
- seed-capital phase 02-reset-onboarding: обнуляет в постгресе
onboarding_*_done и удаляет onboarding_*_hash в extensions.capital.
Без неё bootExtra() через initExtensionsInPostgres ставит все done=true
как dev-shortcut, и UI считает онбординг председателя завершённым
(CapitalOnboardingCard прячется). Нужен прямо обратный 02-extension-config
— поэтому отдельная фаза, а не модификация старой.
- shoot-blagorost-all.mjs: adaptation-chairman добавлен в очередь перед
adaptation, чтобы успеть снять CapitalOnboardingCard до того как
следующие сценарии через 02-extension-config зальют все done=true.
- adaptation.mjs: исправлены селекторы (.hour-option через nth(3),
без невалидной запятой в waitForSelector).
project-create: после клика «Создать» в диалоге компонента сценарий
ждал по `text=MVP v1`, который матчился прямо в открытом диалоге (поле
«Название компонента») и проскакивал мимо реального события закрытия.
Теперь явно ждём пока модалка с заголовком «Название проекта» /
«Название компонента» исчезнет из portal-диалогов. Если за 20с не
закрылась (парсер отстал на свежей цепочке) — закрываем Esc'ом и идём
дальше: компонент уже создан на chain, ждать UI бесполезно.
Также для шага 11 (вход в компонент) перестали ходить во вкладку
«Компоненты» проекта — председатель сам только что создал проект,
clearance к нему ещё нет, и страница отдаёт «Нет компонентов».
Возвращаемся в Мастерскую и кликаем компонент из общего списка.
adaptation: подписан wallet-онбординг через DOM-цикл с ожиданием
появления каждого нового диалога (CapitalRegistrationPage монтируется
раньше первого диалога, готовый dismissOnboardingDialogs выходил
сразу). Wallet-онбординг теперь честно проходится, но **сценарий всё
равно падает**: в текущем UI (CapitalRegistrationPage.vue) старого
заголовка «Адаптация к работе с программой» больше нет — председателю
показывается заглушка «Ранним участникам», пайщикам — мастер выбора
ролей. Это переработка сценария + adaptation.md под новый UI, выходит
за scope текущего фикса.
Прогон через `pnpm shoot:blagorost --reboot` — 12 из 14 сценариев
успешно. На обновлённых скринах больше нет кнопки «Принять участие
(J)» в правом нижнем углу там, где её не должно быть (artifacts /
voting / results) — фикс был добавлен в prepare ранее.
Скриншоты обновлены для: artifacts, clearance, commits, investments,
master-and-plan, profile, project-create (5/6, последний — старый),
projects-list, results, tasks, voting. Также свежий
commits/04-approve-dialog.png.
Оркестратор bin/shoot-blagorost-all.mjs:
- adaptation теперь идёт первым (страница «Адаптация» видна только до
того, как любой 02-extension-config поставил *_done=true);
- между группой «до голосования» и «голосование/результаты» делается
второй reboot — после commits-master компонент уходит в состояние,
где seed-фаза 08-investments перестаёт быть идемпотентной и валится
с «нужен rfrshsegment»;
- при выборочном --only=... reboot между группами не делаем —
пользователь сам отвечает за состояние стенда.
bin/shoot.mjs: controller health-check 90с → 180с — после reboot:extra
coopback грузит контракты ~2 мин, 90с не хватало (первый сценарий
после --reboot падал на ECONNREFUSED).
Известные fail'ы (не блокеры):
- adaptation — на свежей цепочке у председателя горит wallet onboarding
(privacy/wallet/signature/user), который перекрывает страницу
«Адаптация». Нужна доработка сценария: либо снимать диалог, либо
предварительно отмечать базовые agreements *_done. Старые
скриншоты adaptation остались (не критично — проза не менялась);
- project-create — упал на финальном клике по компоненту, но первые 5
кадров получены и положены в docs руками. 6-й остался от прошлой
версии.
Добавил bin/shoot-blagorost-all.mjs: оркестратор, который прогоняет все
сценарии docs/new/blagorost/** в порядке естественного жизненного цикла
компонента (profile → adaptation → … → result-submit → results).
--reboot применяется один раз на первом сценарии, дальше всё идёт по
накопительному стенду — seed-фазы идемпотентны, ничего не теряется.
После каждого успешного сценария — install в components/docs/.
Падение одного сценария не останавливает остальные; в конце сводка.
Алиас в package.json: `pnpm --filter @coopenomics/docs-harness shoot:blagorost`.
Параллельно: добавил `capital:07b-clearance-all` в prepare сценариев
artifacts.mjs / commits.mjs / results.mjs. Без него пайщик заходил на
страницу без допуска и в правом нижнем углу торчала кнопка «Принять
участие (J)» — критично, потому что некоторые из этих экранов реально
доступны только участникам с допуском (страница голосования, внесение
результата).
Пользовательская документация в docs/new/blagorost/** не должна
светить контрактные действия, имена полей и внутренние термины. Прошёл
по всем страницам и убрал:
- capital::approvecmmt / pushrslt / pushresult / signact1/signact2 /
refreshsegment / convertsegm / addproject / setmaster / setplan /
startproject / openproject / createcmmt / startvoting / vote /
removeproject — заменил на формулировки в терминах кнопок и статусов;
- available_for_program / available_for_wallet — переписал по смыслу
(results.md, wallets.md);
- ResultSubmissionService.generateCombinedData / ResultDocumentPayloadV2
/ capital_results / SHA-256 / on-chain — убрал техничку про сборку
РИД, оставил суть «отпечаток в блокчейне» (intellectual-property.md,
artifacts.md);
- Project hash / parent hash / метаданные — снёс admonition «что не
нужно заполнять руками» из project-create.md.
В lifecycle.md убрал отдельную колонку «Действие контракта» из «карты
переходов» — она дублировала «Что нажимает» техническими именами.
В tasks.md убрал избыточную матрицу разрешённых переходов, унифицировал
роли: «submaster / Ответственный» → просто «Исполнитель» (по сути это
главный исполнитель, но в пользовательской документации эту тонкость
не разворачиваем). Главных практических следствий и таблицы статусов
достаточно.
Phase 15 (15-active-component): готовит компонент «Минимальный продукт»
под проектом «Приложение Стол Заказов» в статусе Active без коммитов:
clearance + setmaster=ant + setplan + startproject. Состояние «работа
открыта, коммитов ещё нет» — для UI-проверок и docs-harness.
07b: поднял sleep между getclearance и confirmapprv с 700ms (1×block)
до 1500ms (≥3×block). Эмпирически 700ms ~1 раз из 6 терял строку в
capital_appendixes — мастер на UI видел «Принять участие» вместо
действий. Заплатка вокруг особенности SHIP, см. memory
project_parser_loses_appendix_deltas.md.
Тон вернул к зафиксированному стилю (живой «Вы», без техники):
- убрал q-select из описания статуса — теперь компактный chip с
выпадашкой;
- словарь: «оценка часов» → «чип времени (факт/план)», «estimate» →
«оценка», «Sidebar задачи» → «Страница задачи»;
- активные глаголы вместо безличных оборотов.
Доописал inline-действия со строки доски (после редизайна IssueListRow):
- клик по чипу статуса — выпадашка переходов;
- клик по чипу времени — редактирование оценки (для мастера);
- клик по аватаркам / «+» — выбор исполнителей.
Создание задачи — плавающая кнопка «+» в правом нижнем углу или
горячая клавиша T (вместо «кнопки в шапке доски»).
alt-описания скриншотов привёл в соответствие с реальным состоянием
(статус «Выполнена», счётчики «Моё время» и т.д.). Скриншоты не
перегенерировал — они актуальные после UI-фиксов 28 апреля.
Раньше «чистая мастерская» seed (01+02+04+04b) использовала
initExtensionsInPostgres() dev-shortcut — он клал в extensions.capital
ХАРДКОЖЕНЫЕ хеши шаблонов с *_done=true. UI считал онбординг пройденным
и пускал в Мастерскую, но как только пользователь шёл по любому
soviet-onboarding flow в реальной UI, controller'ные verify-утилиты
падали на «Сгенерированный документ с хешем X не найден» — потому что в
монге документов с такими хешами не было.
Чиню это полностью честно:
- index.ts: регистрирую phase 02b в диспетчере как `02b-real-onboarding`
(было: только импортирована, но не подключена).
- phase 02b: проводит ВСЕ 5 шагов адаптации через настоящий soviet vote
flow (Generate → Propose → 3×Vote → Authorize+Exec). Заменил fakeDocument
на РЕАЛЬНУЮ генерацию протокола FreeDecision (registry_id=600) через
Mutations.Documents.GenerateDocument с обязательными decision_id +
project_id (из meta решения), потом client.Document.signDocument(...,
CHAIRMAN, 1) — реальная подпись председателем. Чейн возвращает hash в
lowercase, controller — UPPERCASE: case-insensitive lookup. Между
votefor и FreeDecision генерацией — retry с pause (parser-индекс
отстаёт ~700ms-3s, без него factory.getDecision падает на «Голоса за
решение не найдены»). Перед отправкой meta стрингифай (eosjs strict).
- phase 04b: переписал на `Mutations.Chairman.ConfirmApprove` с РЕАЛЬНЫМ
approved_document. Чтение оригинального approval.document из
postgres.chairman_approvals (ant'ова подпись id=1), затем добавление
второй подписи (signatureId=2) через client.Document.signDocument —
ровно как делает desktop'овский useConfirmApproval. Retry на ожидание
parser-индексации. fakeDocumentSignedBy выпилен.
Минимальный seed «чистая мастерская» теперь:
reboot:extra (5 членов совета — нужны для голосов)
→ seed-capital 01-programs 02b-real-onboarding 04-contributor 04b-approve-contributor
Все хеши в pg/mongo соответствуют реальным документам — UI flow «Принять
договор УХД» больше не получает «не найден».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 04 регистрирует Contributor через Capital flow, но УХД остаётся в
status=pending. При обычном boot:extra (5 членов совета) утверждение
проходит через стандартный flow голосования; для plain boot (один
председатель) этого не происходит — любая попытка обновить ставку/часы
валится с «Договор УХД с пайщиком не активен».
Новая фаза 04b делает один soviet::confirmapprv с approval_hash =
contributor_hash, подписанной председателем — УХД переходит в active.
Идемпотентно (no-op если уже active).
Минимальный «чистая мастерская» seed теперь:
seed-capital 01-programs 02-extension-config 04-contributor 04b-approve-contributor
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
На ComponentTasksPage список задач делит экран с левой боковой панелью
(Статус/Мастер/Видео/Репозиторий) — реальная ширина списка ~600px CSS.
В таком layout actions-блок (chip статуса + аватарки) уезжал за правый
край: html-table без table-layout:fixed подгонял колонку под intrinsic
width содержимого, длинный title распирал строку шире контейнера.
- IssuesListWidget: table-layout: fixed; width: 100% + overflow: hidden
на q-td — таблица заперта в контейнере, IssueListRow flex layout
корректно ужимает title.
- IssueListRow: flex-wrap: wrap + title flex-basis 200px / min-width 200px
— на достаточно широких контейнерах одна строка, на узких actions
переезжает на вторую без клиппинга.
- tasks.mjs scenario description: пояснил, что в кадре 01-board виден
новый компактный layout (приоритет → ID → чип времени → тайтл → chip
статуса → аватарки).
Сценарий blagorost/tasks: 5 shots сняты успешно, аватарки центрированы,
title корректно ellipsis-ит на узких строках.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- IssueStatusChip: оптимистичный UI — при выборе нового статуса chip
перекрашивается мгновенно (через локальный optimisticStatus ref), не
ждёт round-trip. Watch на props.modelValue сбрасывает override, когда
store догоняет. Сохранение через saveImmediately (без 2s debounce) —
статус discrete, не текст. На время round-trip — мини-спиннер в chip,
ошибка → откат к предыдущему статусу.
- SetCreatorAvatars: триггер фикс-ширины 72px (2 аватарки + overflow «+N» =
3 круга). Без фикс-ширины колонка прыгала row-to-row, статус-chip уезжал
влево/вправо при разном числе исполнителей. visibleCreators сокращён с 3
до 2 — дальше «+N» в одном кружке, чем 4 наезжающих инициала.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- IssueTimeChip — фикс-ширины (70px) триггер с иконкой schedule + компактным
«факт/план». Title больше не «прыгает» между задачами с разным estimate.
Клик → q-menu с инпутом плана (estimate, debounce save через useUpdateIssue)
и read-only факта с прогресс-баром. Факт не задаётся вручную (read-only,
считается из TimeEntry) — это вынесено в hint в попапе.
- SetCreatorAvatars: q-avatar заменён на чистый div. Quasar внутри
q-avatar__content использует position: absolute; inset: 0, и при наличии
border на родителе содержимое визуально съезжает из центра. Свой div с
display: flex; align-items: center; justify-content: center даёт ровный
центр инициала.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- meta-блок теперь горизонтальный: приоритет → ID → время. Время с
прогрессом без иконки-часов (no-icon в Estimation), не утяжеляет строку.
- Аватарки исполнителей укрупнены 22→28px; принудительный flex-center
внутри q-avatar__content + box-sizing: border-box — инициалы по центру,
не уезжают к правому-нижнему углу.
- Меню смены статуса: внутренние отступы (header «Сменить статус», padding,
gap, hover, border-radius пунктов), маленький цветной кружок (q-icon
circle) вместо «голого» badge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Переделал список задач: meta-блок (id + приоритет, под ними оценка/факт),
title растягивается на всё свободное пространство, действия справа сжаты до
chip-статуса и стопки аватарок исполнителей.
Новые компоненты:
- IssueStatusChip — маленький цветной chip-триггер; q-menu со списком разрешённых
переходов (ровно те же permissions/allowed_status_transitions). Логика
обновления через useUpdateIssue (debounce + откат при ошибке) — как в
UpdateStatus.
- SetCreatorAvatars — стопка аватарок (до 3 + «+N»). По клику открывается q-menu
с существующим ContributorSelector — multi-select / поиск / автосохранение
через useSetCreators сохраняются.
- IssueListRow — общая строка для обоих режимов виджета (полноэкранный с
virtual-scroll и компактный встроенный). Mobile-фолбэк на ширине ≤640px:
meta+title в первой строке, action-блок переносится во вторую.
Старые UpdateStatus / SetCreatorButton оставил — они используются на
страницах деталей задачи; в списке заменены на компактные варианты.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Колонка estimate в IssuesListWidget зафиксирована (width 110px, flex-shrink 0,
overflow hidden) — Estimation с прогресс-баром больше не распирает соседнюю
колонку с заголовком/приоритетом. В Estimation у progress-bar убрал min-width
60px (заменил на width 100%/max-width 90px) и добавил min-width 0 на
контейнер — теперь компонент сжимается под родителя.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* tasks.md — Agile-механика внутри Компонента: 6 статусов (BACKLOG/TODO/
IN_PROGRESS/ON_REVIEW/DONE/CANCELED), матрица переходов 6×6 с ролями
Мастер/Ответственный/Исполнитель/Совет, права (только Мастер ставит
estimate/priority и закрывает в DONE), два режима учёта времени —
без estimate (час/час, поделено на активные задачи) и с estimate
(estimate/N исполнителей при → DONE).
* mkdocs.yml — пункт «Задачи и план работ» в навигацию Благороста.
* doc-shoot scenarios/blagorost/tasks.mjs — 5 кадров: доска задач,
диалог «Создать задачу», sidebar мастера и исполнителя, страница
«Моё время» со счётчиками (Доступно / В ожидании / Подтверждено).
* seed-capital phase 09: задача estimate=0 в IN_PROGRESS — иллюстрирует
почасовое начисление «время по факту» для исследовательских задач.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs/blagorost: новые страницы — clearance, lifecycle, wallets; расширены
artifacts (4 формата), voting (Водянов), results (внести результат, акт),
commits (вид мастера); mkdocs.yml — навигация под них.
* docs-harness: 4 новых сценария (clearance, commits-master, result-submit,
voting перепрошит); набор скриншотов под все 4 формата артефактов.
* seed-capital: фазы 07b-clearance-all, 09b-artifacts, 10a-pending-commit
для seed чистого blagorost-стейта без ручных кликов.
* 07b: sleep 700ms между getclearance и apprvappndx — обходим SHIP-баг,
при котором insert+erase contract_row внутри одного блока не эмиттит
дельту (net state change = 0). См. memory project_parser_loses_appendix_deltas.
* controller: возвращена 3-сек задержка emit'а action-события — даёт
дельтам того же блока (capital_appendixes) сохраниться раньше, чем
обработчики action полезут читать состояние.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Убрал разговорную и поучительную интонацию, оставил суть деловым языком: кооператив принимает имущество у одного и передаёт другому, контрагентом для обеих сторон выступает сам кооператив.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Переформулировал purpose: «исполнение заказа» — это не обмен между пайщиками через платформу, а одновременное удовлетворение кооперативом двух потребностей (одного — поставить, другого — получить), стороны напрямую не взаимодействуют. Это суть кооперации.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В шапке страницы стандарта остаётся только сам абзац-вступление; тех. перечисление сущностей удалено как мусор.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Везде заменил многословный технический рассказ про действия / операции / wallet-коды / Дт-Кт на один абзац-смысл — что это за процесс, кто им пользуется и зачем. Детали уже есть в карточках действий, статусов и операций — повторять их в шапке избыточно.
Затронутые стандарты: p.wal.depo, p.wal.wthdrw, p.cap.invest, p.cap.debt, p.cap.prop, p.cap.rid, p.mkt.reqst, reg.coop, sov.authpkg, sov.decision, sov.selectbranch, meet.hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Высота диаграммы убрана из clamp-кепа 820px — теперь это calc(100vh - 240px) с min-height 520px. На мониторах 1440p+ диаграмма занимает всю доступную высоту, а не 2/3 экрана.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
— На карточке действия с двумя операциями (типа confirmreg → o.reg.payent + o.reg.putmin) операции теперь сидят рядом в две колонки (grid auto-fit minmax 180px), а не стопкой.
— Внутри каждой операции «Проводки» и «Переводы» тоже разнесены в две колонки — компактные значения «Дт/Кт» и «∅ → wallet» больше не занимают по строке каждый.
— Колонке операций отдан больший вес во flex-раскладке (flex 1.5, min 280px), чтобы не уезжала на следующую строку, когда рядом ещё и блок документов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Оставлен один абзац: что это за процесс и зачем он нужен. Детали (участники, операции, составная сущность) уже описаны в карточках действий, статусов и операций — повторять их в шапке избыточно.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
— ProcessPage: новая секция «Контракт, процесс, сущность, статус, стандарт» над диаграммой; туда переехал длинный purpose с разрывом абзацев (white-space: pre-line). Краткий summary убран — он дублировался.
— FocusBar: режим process-start больше не рендерится — на свободном (не сфокусированном) состоянии диаграмма видна полностью; оверлей внизу появляется только когда выбрано действие, статус, документ или операция.
— p.reg.accept: actions[].purpose / states[].description / operations[].description обрезаны обратно до tagline (1–2 фразы) — карточки на диаграмме больше не «раздуваются».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Терминологическая ошибка: я представлял приёмку РИД как «два акта»
(акт о результатах + акт приёма-передачи), но фактически это **один**
документ — Акт приёма-передачи РИД (`ResultContributionAct`), на
котором стоят **две подписи**: signact1 — пайщика, signact2 —
Председателя.
Заодно явно перечислены три документа всей приёмки:
• Заявление о паевом взносе РИДом — пайщик подписывает на pushrslt;
• Решение Совета о приёмке — стандартная цепочка через Совет;
• Акт приёма-передачи РИД — единый документ, две подписи.
В таблице ролей `results.md`: «подписать два акта» → «подписать
Акт приёма-передачи РИД».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
— p.reg.accept переписан в стиле «tagline + развёрнутый абзац» для summary, purpose, actions[].purpose, states[].description, operations[].description, related[].note и scenario.steps[].description; сохранены все структурные поля.
— FocusBar: gateway_operator → «Кассир» в человеческих лейблах; .focus-bar__desc получил white-space: pre-line, чтобы пустая строка в YAML рендерилась как разрыв абзаца.
— p.wal.depo: «оператор Gateway» → «кассир» в прозе шага оплаты.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Прошёлся по страницам Благороста и привёл термины к официальному
словарю (`components/blago-cli/_blago/.../c5-slovar-kooperativnoy-
ekonomiki-v1.md`):
• «программа Благорост» → **ЦПП «Благорост»** (программа
кооперативной НИОКР, куда уходит складочный капитал
`contributors_bonus_pool`); «Главный кошелёк» (ровно по словарю).
• «соавторы» (как множественное от Соавтор) → **Авторский
коллектив** (Автор + Соавторы + Мастер) — там, где речь о
распределении базы/бонусов на роли is_author. По словарю «Соавтор»
— это конкретно экспертно-творческое участие; для общего пула
точнее «Авторский коллектив».
• Введён термин **Складочный капитал ЦПП «Благорост»** для
обособленного учёта `contributors_bonus_pool` — это уже было в
словаре, но в моих текстах не использовалось.
• **Билет времени** — упомянут как учётная единица астрономического
времени (ровно как в словаре).
• В voting.md явно назван **метод Водянова (система «Компас»)** со
ссылкой на Положение о ЦПП «Благорост».
Доля в ОАП — переформулирована как «закреплённая часть стоимости
ОАП, право требования возврата паевого взноса. **НЕ доход и НЕ
авторские права**» — точно по словарю и многократно повторённой
просьбе пользователя.
Формула стоимости РИД (intellectual-property.md) переписана под
словарные термины:
A = (1 + φ) × (v + v′) × t — генерационная часть (Исполнители +
Авторский коллектив, базы + бонусы)
B = φ × A — Складочный капитал ЦПП «Благорост»
total_contribution = A + B — стоимость РИД, паевой взнос Компонента
Также добавлены термины в локальный словарь (файл .gitignore'нут,
живёт в blago-cli — не пушится с этим коммитом, но пользователь
может перенести их в исходный репо словаря): Артефакт, Астрономическое
время, Доля в ОАП, Общественно-полезное (созидательное) время,
Результат интеллектуальной деятельности (РИД).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Две новые страницы документации Благороста:
«Артефакты» (`artifacts.md`):
• Что такое артефакт — фрагмент описания работы (текст, диаграмма,
схема), создаётся авторами и исполнителями.
• Где живут — вкладка «Артефакты» на проекте (общие документы) и на
компоненте (специфичные требования / диаграммы / мокапы).
• Четыре поддерживаемых формата: Markdown (основной), Mermaid
(отдельный артефакт-диаграмма), BPMN (бизнес-процессы), drawio
(произвольные схемы); таблица «когда какой формат».
• Как создать (диалог + Ctrl+Enter / ⌘+Enter).
• Как они влияют на формирование РИД.
«Результат интеллектуальной деятельности» (`intellectual-property.md`):
• Что такое РИД — формальный документ, не «ссылка на коммит». Состав:
project.description + Stories (артефакты проекта/компонента) + Issues
+ Commits с git-дифами и текстом коммитов + meta.
• Хэширование SHA-256 в `ResultSubmissionService.generateCombinedData()`,
публикация хэша в blockchain через `capital::pushrslt`, хранение
полного текста в БД `capital_results`.
• Приёмка через два акта подписи: signact1 (пайщик подтверждает свой
вклад) и signact2 (председатель принимает РИД от лица кооператива);
между ними — формальное решение совета.
• Стоимость РИД — формула с публичного сайта программы благорост:
A = (1 + φ) × (v + v′) × t для генерационной части и B = φ × A для
доли Благороста; φ = 0.618 (контракт-константа AUTHOR_BASE_COEFFICIENT).
• Два класса времени: профессиональная компетенция (v) + общественно-
полезное (v′), оба в единой ставке `hour_cost`.
• Соавторы получают 0.618× базы исполнителей; бонусы голосования по
методу Водянова добавляют ещё столько же.
• Доля Благороста (`contributors_bonus_pool = 0.618 × total_generation_pool`)
распределяется между всеми пайщиками программы пропорционально
их предыдущим долям в ОАП — это «доля в объекте авторских прав»,
не «доход» и не «авторские права».
Сценарий docs-harness `blagorost/artifacts.mjs` — два кадра (вкладка
«Артефакты» компонента и проекта). На seed-данных артефакты не
создаются, поэтому показ — пустой страницы с кнопкой создания.
`mkdocs.yml` — два новых пункта в разделе «Благорост»: «Артефакты»
и «Результат интеллектуальной деятельности».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- p.cap.prop: 1070 заявление + 1071 решение совета + 1072 акт ×2
(имущественный взнос в Благорост-капитализацию)
- p.cap.debt: 1050 заявление + 1051 решение (двойная подпись:
председатель → совет; в реестре одна форма GetLoanDecision)
- p.wal.wthdrw: 900 заявление + 901 решение (двойная подпись)
Поправлены и тексты под человеческие title из реестра. Поле template:
убрано — все три файла переходят на slim-схему с registry_id.
Реестр кошельков перешёл с числовых id на eosio::name-строки
(w.wal.share, w.cap.bgrid и т.д.) — UI был ещё в старой схеме:
- WalletId: number → string (соответствует cooptypes/ledger2)
- getWallet ищет по поле `name`, не `id`
- walletTitle: вывод meta.human_name (вместо meta.name = идентификатор)
- walletDisplayId / v-if «Переводы»: пустая строка == null → ∅
(раньше пустая wallet_from при ISSUE прорывалась как пустая ячейка)
- reg.coop.standard.yaml: числовые 2001/3003 → w.wal.share / w.sov.delgte
Вместо технического process_type (`p.reg.accept`) показываем title
стандарта («Приём пайщика»), а сам код вынесен мелким моно-бейджем
сбоку. Relation-слова уже были по-русски.
Доразвитие фикса 3219459b8e. В прошлой версии при свежей БД
(`startBlock=1 && currentBlock=0`) я перенёс purgeAfterBlock внутрь
условия и не оставил его для производственного hot-restart-сценария
(`currentBlock>0`) — это могло пропустить очистку orphan-записей при
fork/replay в проде.
Корректный фикс: вернуть финальный `purgeAfterBlock(currentBlock)`
после `if`-блока (как было в оригинале) и устранить race c Initializer'ом
изящно — Initializer теперь принимает `block_num` параметром и не делает
свой собственный getInfo(). Reader передаёт ему тот же currentBlock,
что использует для финального purge; purge применяет $gt (а не $gte),
поэтому записи Initializer'а с block_num == currentBlock остаются.
Поведение всех трёх веток:
• startBlock=1, currentBlock=0 (свежая БД): Initializer пишет
с block_num=currentBlock; финальный purge оставляет его данные.
Раньше Initializer писал с head_2 > head_1 и эти данные стирались.
• startBlock=1, currentBlock>0 (прод hot-restart): Initializer не
вызывается; финальный purge подчищает orphan-записи > currentBlock —
идентично оригиналу.
• startBlock!=1 (debug-replay): currentBlock = startBlock; финальный
purge — идентично оригиналу.
Удалена локальная функция getInfo() в Initializer'е (больше не нужна).
Проверено: после restart на прод-сценарии (currentBlock=2077) deltas
для registrator/coops (3 записи) и soviet/boards (1) сохраняются;
Initializer корректно пропускается т.к. данные уже есть.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Корневой блокер для seed-фаз через docs-harness:
• parser/Reader: после reboot:extra Reader дёргал getInfo() и брал
head_block_num как currentBlock; затем initializeFromBlockchain()
делал getInfo() ВТОРОЙ раз и записывал deltas с block_num=head_2
(head за это время мог шагнуть). Reader потом вызывал
purgeAfterBlock(currentBlock=head_1), стирая все Initializer-данные
с block_num > head_1 — Mongo оставалась пустой по cooperatives/boards,
factory.Cooperative.getOne() падал «Совет кооператива не обнаружен»,
seed-фаза 04 не могла зарегистрировать Contributor.
Фикс: purgeAfterBlock ВЫЗЫВАЕТСЯ ДО initializeFromBlockchain. Теперь
дельты Initializer'а попадают в Mongo и не стираются.
Заодно:
• seed/13-push-result: voter-action data теперь содержит `username:
voter` (а не остаточный `username: ant` из fakeVote). Без этого
`has_auth(username)` в votefor.cpp возвращал true для ant, но false
для остальных — контракт переходил к require_auth(coopname), а в
authorization массиве coopname'а не было — «missing authority of voskhod».
• docs-harness/scenarios/blagorost/results.mjs: убран дубликат-кадр
`02-segment-actions` (UI этапа «Результат» — список карточек, не
таблица; селектор tr:has-text не срабатывал). Остались два кадра:
`01-overview` (страница «Результаты» с ColorCards и сегментами) и
`02-convert-dialog` (диалог «Получить долю в ОАП» со слайдером).
• docs/new/blagorost/results.md: вставлены image-references для обоих
кадров; уточнены подписи под актуальный UI (вкладка «Результаты»,
статус «Приёмка», читаемые названия пулов на ColorCards), добавлено
пояснение «100% в Благорост» когда available_for_wallet=0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Сценарий blagorost/results.mjs — три кадра пост-voting пути:
• 01-overview — страница «Результат» (ColorCards + таблица сегментов);
• 02-segment-actions — раскрытый сегмент пайщика с «Получить долю в ОАП»;
• 03-convert-dialog — открытый ConvertSegmentDialog со слайдером
«Главный Кошелёк ↔ Программа Благорост».
fixture lazily читается в default(), чтобы не падать на module-load
после reboot (когда фикстура ещё не создана orchestrator'ом). Указаны
`fixture: 'ivanpetrov'` + `fixtures: ['ivanpetrov', 'ekaterina']` —
обе нужны фазе 05.
Фаза 07 (master-and-plan):
• input GetProjectWithRelations — `projectHash` (camelCase, без coopname)
под актуальную schema контроллера.
• Polling до 60с пока parser не индексирует createproject — иначе
capitalSetPlan падает с «Проект ... не найден» сразу после фазы 06,
у которой нет ожидания catch-up.
Фаза 13 (push-result):
• processLastDecision теперь читает `soviet::boards` и голосует от
каждого voting-члена совета (один WIF подписывает за всех — у всех
общий default_public_key). Без этого после reboot:extra (5 членов
совета) `soviet::authorize` падает с «Консенсус совета по решению
не достигнут».
Известный блокер для скриншотов results — отдельная регрессия парсера:
после reboot:extra parser стартует с позднего блока (currentBlock~1070)
и пропускает createboard/regcoop, так что Mongo `cooperatives`
остаётся пустым; controller `Cooperative.getOne` падает «Совет кооператива
не обнаружен», и фаза 04 не может зарегистрировать contributor. Это не
проблема сценария, и фиксится отдельно — не моими правками.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новая страница, описывающая весь пост-voting путь компонента:
• Этап «Результат» — что появляется на странице, поля сегментов;
• Пересчёт результатов — служебная кнопка обновления арифметики
после закрытия голосования или изменения профиля пайщика;
• Внесение результата мастером (хоткей R) — фиксация долей,
обязательства паевого взноса РИДом, автоматический зачёт
активных займов кооператива;
• Признание совета — зачем нужен formal-flow председателя
(confirmapprv → voteFor → authorize → exec);
• Получение доли пайщиком — два акта (о результатах + приёма-
передачи РИД), затем слайдер «Главный кошелёк / Благорост»
как механизм конвертации направлений;
• Удаление компонента после полной конвертации.
В `voting.md` добавлена сквозная ссылка на новую страницу: после
голосования читатель сразу понимает, куда переходит компонент
и какие действия его ждут как пайщика/мастера/председателя.
`mkdocs.yml` — новый пункт «Результат и получение доли» в разделе
«Благорост», расположен после «Голосования».
Скриншоты будут добавлены отдельным проходом docs-harness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Две новые страницы документации компонента «Благорост»:
• `commits.md` — пайщики добавляют коммиты в активный компонент через
трекер, мастер одобряет; каждый одобренный коммит формирует РИД-обяз.
на полную сумму на w.cap.gncom.
• `voting.md` — переход компонента в голосование, как пайщики
распределяют голоса по методу Водянова, расчёт voting_bonus и
автоматическое закрытие.
В `investments.md` дочищена терминология: чёткое разделение «деньги»
vs «РИД» в путях паевого взноса.
Сценарии docs-harness под новые страницы:
• `blagorost/commits.mjs`, `blagorost/voting.mjs` — съёмка серии
скриншотов из desktop-кабинета пайщика и мастера.
• `lib/harness.mjs`, `adaptation.mjs`, `investments.mjs`, `profile.mjs` —
подкручены селекторы и шаги под текущий UI; снимки лежат в
`assets/new/blagorost/{commits,voting}/`.
`mkdocs.yml` — добавлены два новых пункта nav в раздел «Благорост».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Расширяем seed-сценарий за пределы инвестиций: компонент проходит
весь путь от задач до приёмки и конвертации направлений, чтобы
docs-harness снимал последовательные шоты для документации.
Новые фазы:
09 — `tasks` — четыре задачи мастер-плана с коэффициентами;
10 — `commits-and-voting` — коммиты исполнителей + переход в голосование;
11 — `cast-votes` — равномерное распределение голосов всеми участниками
и автоматическое закрытие через cmpltvoting;
12 — `recalc-and-calc-votes` — rfrshsegment + calcvotes по каждому;
13 — `push-result` — pushresult всеми пайщиками + soviet-цепочка
(confirmapprv → voteFor → authorize → exec)
+ signact1/signact2 на каждый сегмент;
14 — `convert-segments` — convertsegm с разной долей walletAmount/
capitalAmount по пайщикам; финализация
проекта.
Параметризация увеличена под более «сочный» демо-кейс:
• hour_cost 1500 → 5000 RUB (07-master-and-plan)
• размеры задач 8/6 → 30/20 часов (09)
• коммит-часы 8/6 → 30/20 (10)
• investAmount petrov 30K, ekaterina 10K (08)
`rotate-participant-keys.ts` — служебная утилита: после reboot:clean
WIF фикстур теряется; скрипт делает registrator::changekey + сохраняет
новый keypair, чтобы повторный seed работал поверх свежей цепочки.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После полного цикла commits→voting→pushresult→signact2→convertsegm для
комплексного компонент-проекта проверяем, что Σ COMMIT_RID(коммитов) >=
Σ ACCEPT_RID(сегментов): кошелёк w.cap.gncom (ЦПП «Генератор» — коммит)
по дельте проекта не уходит в минус.
Жёсткий инвариант — отсутствие дефицита (delta >= 0). Если он нарушен,
значит approvecmmt кладёт на w.cap.gncom меньше, чем signact2 забирает
у пайщиков — регрессия патча approvecmmt; в проде это «недостаточно
средств на кошельке GENERATOR_COMMIT» на signact2 последнего пайщика.
Эквивалентность delta == 0 не проверяется — investor3 идёт через
purgesegment без конвертации, тестеры с is_contributor=0 не получают
свою долю contributors_bonus, эта непокрытая часть остаётся как остаток
(в скрипте логирует ⚠️). Это допустимо для сложного сценария теста.
Дополнительно: sanity-чек на w.cap.bgrid (BLAGOROST_RID) — путь
ACCEPT_RID(w.cap.gncom → w.cap.bgrid) после signact2 хоть кого-то
перенёс средства.
Идентификаторы кошельков — eosio::name (рефакт 184530dab7). Старые
numeric ID 10001/9002 в коде не используются; wallets.id теперь string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше approvecmmt после `Projects::add_commit` перечитывал segment и
эмитил COMMIT_RID на дельту `available_for_program` — это покрывало
только base/bonus создателя на момент коммита. Когда позже calcvotes
рассчитывал voting_bonus и докидывал `available_for_program`, signact2
последнего пайщика проекта забирал больше, чем лежало на w.cap.gncom
(GENERATOR_COMMIT) — walletop падал с «недостаточно средств».
Теперь COMMIT_RID эмитится на `commit.amounts.total_contribution` —
полную стоимость коммита (base+bonus всех ролей и contributors_bonus).
В сумме по всем коммитам проекта это ровно `project.fact.total_contribution`,
которое = Σ intellectual_cost всех сегментов; signact2 спокойно забирает
свою долю независимо от того, как голосование перераспределило бонусы.
Инвариант: Σ COMMIT_RID(коммитов проекта) == Σ ACCEPT_RID(сегментов).
GENERATOR_COMMIT (w.cap.gncom) закрывается в ноль на проекте, не на
конкретном сегменте.
Заодно убрана пост-modify выборка сегмента по `commit.project_hash` /
`commit.username` (secondary index по checksum256+name) — она давала
runtime «access violation» в WASM (multi_index secondary итераторы
становятся невалидными после modify; валится на eos-vm.cpp:165).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seed-capital фаза 08-investments — программно подготавливает стенд для съёмки:
- soviet::sndagreement для petrov+ekaterina (соглашения wallet и blagorost
ЦПП), создаёт пользовательские кошельки
- capital::getclearance + soviet::confirmapprv — приложения к УХД-договору
для инвесторов
- capital::startproject + capital::openproject — компонент в active со
включённым приёмом инвестиций
- wallet::createdeposit + gateway::completeincome — пополняет кошельки
инвесторов (600 000 ₽ petrov, 300 000 ₽ ekaterina)
- capital::createinvest — petrov 500 000 ₽ → компонент MVP v1
- capital::createpinv — ekaterina 200 000 ₽ → программа «Капитализация»
Сценарий blagorost/investments.mjs снимает 3 кадра:
- toggle «Принимает инвестиции» включён в sidebar (выделен)
- вкладка «План» компонента: строка «Привлекаемые инвестиции» в колонке
«Факт» с реально поступившими средствами
- профиль с выделенной кнопкой «Инвестировать» под Кошельком Благороста
Проза investments.md описывает два пути инвестирования:
1) В компонент — пайщик сам приоритизирует, кнопка «Инвестировать (I)»
на карточке компонента
2) В программу «Капитализация» — кнопка «Инвестировать» на профиле,
совет распределяет средства между компонентами
Таблица сравнения двух путей; admonition про источник средств (основной
кошелёк) и про разделение паевых взносов пайщиков и финансирования
от кооператива.
mkdocs.yml: новая страница в разделе «Благорост» (Инвестирование).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master-and-plan:
- seed-capital фаза 07 — программно одобряет УХД-договоры всех contributors
(soviet::confirmapprv через contributor_hash), подписывает приложение
председателя к компоненту (capital::getclearance + confirmapprv), назначает
мастера (capitalSetMaster) и устанавливает план 160 ч × 1500 ₽ + 50 000 ₽
расходов (capitalSetPlan)
- сценарий blagorost/master-and-plan.mjs снимает 3 кадра:
страница компонента с мастером, вкладка «План» компонента (таблица
план/факт со всеми расчётными пулами от контракта), сводный план проекта
- проза master-and-plan.md описывает роль мастера, три значения плана
(часы/ставка/доп.расходы), как контракт разворачивает их в полные пулы,
что разблокирует переключатель «Принимает инвестиции»
- mkdocs.yml: новая страница в разделе «Благорост»
project-create:
- сценарий расширен: после Мастерской открывает карточку проекта (статус
«Ожидает», sidebar выделен), переключает в «Активен» через UpdateStatus
dropdown, переходит в карточку компонента и активирует её
- через lib/annotate.mjs накладывает красные рамки на ключевые элементы:
«+ Проект (P)» в FAB, «+» в строке проекта, «Статус» в sidebar
- проза уточнена: Active разблокирует только приём коммитов; toggle
«Принимает инвестиции» в sidebar — только после плана; в проекты
напрямую не инвестируем
profile:
- второй кадр с прокруткой к таблице взносов
- проза описывает роли (Соавтор/Исполнитель/Инвестор/Координатор)
и отдельно строку «Получено в Благорост» (доля от других пайщиков
по правилам ЦПП)
infra:
- harness.mjs: shot() возвращает абсолютный path для пост-обработки PNG
(annotate без сериализации в manifest)
- seed-capital/index.ts: phase 07 в реестре фаз
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В ComponentPlanningPage и ProjectPlanningPage watcher на projects.items был без
{deep: true}, поэтому in-place мутации через splice (которыми store обновляет
items в loadProject/addProjectToList) не триггерили watcher. Локальный
project.value оставался stale, и ProjectPlanningWidget показывал «не установлено»
во всех ячейках таблицы план/факт даже когда is_planed=true и plan заполнен в БД.
В useProjectLoader (composables.ts) тот же watcher уже был с {deep: true} —
там UI обновлялся, в этих двух страницах было пропущено.
Дополнительно ComponentPlanningPage.loadProject теперь всегда форсит запрос
на сервер: раньше брал из store если уже там, что давало stale-значения,
загруженные до setMaster/setPlan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
YAML-стандарты:
• удалены p.cap.import («Импорт пайщика», частный кейс ВОСХОДа) и
p.sov.axncnv («Конвертация паевого в делегатский ЧВ», частный кейс
инфраструктуры) — оба не относятся к общему реестру кооперативных
стандартов; ссылки на них вычищены из p.reg.accept, p.cap.invest,
p.mkt.reqst.
• унифицированы названия в стиле «глагол-существительное действия»:
p.cap.debt «Займ пайщику» → «Выдача займа пайщику»
p.cap.invest «Инвестиция в программу» → «Приём инвестиции в программу»
p.mkt.reqst «Запрос маркетплейса» → «Исполнение заказа на поставку имущества»
• в p.reg.accept откачена попытка добавить альтернативный путь через
registrator::adduser — визуально длинная стрелка bypass'а пересекала
main-flow, уверенно расположить её в графе не получилось; в реестре
остаётся одно «Приём пайщика» с одним основным сценарием.
UI standards-site:
• новый файл src/data/labels.ts — общие словари CONTRACT_HUMAN
(registrator → Регистратор, wallet → Главный кошелёк, capital →
«Благорост», marketplace → «Стол заказов», soviet → Совет,
ledger2 → Учёт операций) и STATUS_HUMAN (proposed → предложен,
approved → утверждён, active → действующий, deprecated → устаревший).
• Sidebar и HomePage показывают группы как «русское имя + мелкий
code-бейдж с английским» вместо ведущего ALL-CAPS-тех-имени —
русский становится первичным, тех-код вторичным.
• На главной убраны (1) упоминание ledger2 и process_type из
интро-абзаца, (2) тикер «одноактовый / N операций» с карточек,
(3) дублирующий <code>process_type</code> под названием стандарта;
статус выводится по-русски через statusHuman.
• layout.ts очищен от alt-bypass-логики (rerouted target=END_ID +
post-layout y/x reposition) — она была нужна только под adduser.
Зачем: реестр должен показывать председателю общие кооперативные
стандарты на понятном языке, а не технические артикулы с английскими
кодами. ВОСХОД-специфика и внутренние ledger2-механики из реестра
вычищены — они остаются в C++ контрактах и cooptypes-реестрах, но
не претендуют на статус общекооперативного стандарта.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seed-capital — поэтапная подготовка стенда для doc-shoot. Диспетчер принимает
список фаз и --up-to=<phase> — стенд можно остановить в любой точке для ручного
теста UI на конкретном этапе.
Фазы:
- 01-programs / 02-extension-config / 03-projects (реестр из _blago/INDEX.md + Кошелёк пайщика)
- 04-contributor (председатель ant) / 05-additional-contributors (ivanpetrov, ekaterina)
- 06-create-project-koshelek (рабочий проект для серии «Генерация»)
Сценарии и документация (components/docs/docs/new/blagorost/):
- adaptation, profile, projects-list, project-create
Orchestrator bin/shoot.mjs: поддержка meta.fixtures (явный список пайщиков для
seed-фаз) + meta.prepare (запуск нужных фаз перед сценарием).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
p.reg.accept — добавлен альтернативный однокликовый путь приёма пайщика:
• action registrator::adduser (actor: председатель), role: closer
• transition ∅ → active напрямую, минуя created/payed
• триггерит o.reg.putmin (всегда) и опционально o.reg.payent
(если spread_initial=true)
Это путь для исторических участников и случаев, когда заявление и
решение совета оформлены офлайн-документами; председатель добавляет
пайщика как сразу активного. Под капотом тот же процесс p.reg.accept,
но с другой вилкой на старте.
p.cap.rid — title в Sidebar изменён с «Приём результатов интеллектуальной
деятельности» на «Приём результата интеллектуальной деятельности»
(единственное число) + переименование «акт-1»/«акт-2» в человеческий
язык: «акт приёма-передачи РИД» — один документ с двумя подписями
(первая подпись участника, вторая — председателя).
p.cap.prop — аналогичная семантика акта приёма-передачи имущества: один
документ, две подписи (пайщик первый, председатель второй). Действия,
статусы и тексты сценария переписаны с «акт-1/акт-2» на «первая/вторая
подпись на акте приёма-передачи».
p.cap.debt — упоминания «акт-2 проекта» в описании o.cap.repay заменены
на «акт приёма-передачи проекта».
p.adj.fix — стандарт удалён целиком. Контракт ledger2 описывает
кооперативный учёт операций, а не сами кооперативные операции; ручная
корректировка председателя — внутренний механизм исправления учёта,
а не пользовательский on-chain процесс кооператива. В реестре стандартов
ему делать нечего.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GraphQL-запрос capitalContributors ожидает аргумент options (PaginationInput),
а фронт передавал pagination — неправильный ключ молча пропускался через
индекс-сигнатуру `[key: string]: unknown` в SDK-типе IInput, и backend каждый
раз применял дефолт limit=10. Поэтому q-pagination показывал максимум 1 страницу.
- ContributorsPage.vue: pagination → options в loadContributors и reloadContributors,
descending: boolean → sortOrder: 'ASC'|'DESC'.
- useContributorSearch.ts: pagination → options в loadContributors и preloadContributors,
descending → sortOrder.
Теперь rowsPerPage=25 со страницы реально доходит до бэка, totalCount возвращает
полное число участников и q-pagination показывает все страницы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
State: 180×80 → 220×96, action: 170×54 → 210×68 (плюс синхронизирован
SIZE в graph/layout.ts, чтобы dagre не оставлял nodes налезающими).
Названия (.node-state__human / .node-action__human) — переход с
white-space: nowrap + ellipsis на line-clamp:2 (display: -webkit-box,
-webkit-line-clamp: 2, word-break: break-word). Длинные русские
заголовки вроде «Получение подтверждено», «Решение совета подписано»,
«Авторизовать выплату», «Подтвердить выплату» теперь помещаются
полностью без обрезания троеточием.
Зачем: после реальной заливки 11 стандартов выяснилось, что русские
названия статусов и действий часто длиннее английских артикулов —
дефолтные размеры узлов «съедали» половину текста. Председатель
смотрел на «Получение подтв…» и не понимал, чем оно отличается от
«Получение». Теперь видно полностью.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sidebar.vue:
• .sidebar-body теперь со своим overflow-y: auto (раньше скроллился весь
sidebar и foot уезжал вверх вместе со списком).
• .sidebar-foot — flex: 0 0 auto, чтобы кнопка темы держалась внизу даже
при длинном списке.
• Убрана подпись с process_type под названием стандарта в списке —
идентификаторы вроде «p.cap.invest» в навигации не нужны, они
видны в URL и на странице процесса.
Названия стандартов (top-level title в YAML):
• p.cap.import «Импорт пайщика «Благорост» (offline)» → «Импорт пайщика»
• p.cap.invest «Инвестиция в ЦПП «Благорост»» → «Инвестиция в программу»
• p.cap.rid «Приём РИД в паевой фонд» → «Приём результатов
интеллектуальной деятельности»
Зачем: председатель смотрит сайдбар и хочет видеть человеческие имена
процессов без технических уточнений и без программной привязки к
конкретному ЦПП — детали остаются в самом стандарте, а в навигации —
короткое русское имя.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Покрыты все процессы LEDGER2_PROCESS_REGISTRY кроме p.mig.trans
(одноразовая миграция legacy → ledger2, не нужна как стандарт):
• p.wal.wthdrw — Возврат паевого взноса (5 actions, 2 docs, 1 op)
• p.cap.import — Импорт пайщика «Благорост» offline (1 action, 0 docs, 1 op)
• p.cap.invest — Инвестиция в «Благорост» (1 action, 1 doc, 1 op WALLET_ONLY)
• p.cap.debt — Беспроцентный заём пайщику (6 actions, 3 docs, 2 ops)
• p.cap.rid — Приём РИД в паевой фонд (6 actions, 3 docs, 3 ops)
• p.cap.prop — Имущественный паевой взнос (6 actions, 4 docs, 1 op)
• p.mkt.reqst — Запрос маркетплейса (12 actions, 10 docs, 2 ops)
• p.sov.axncnv — Конвертация паевого в делегатский ЧВ (1 action, 1 doc, 1 op)
• p.adj.fix — Ручная корректировка председателя (2 actions adjustment-kind, 0 docs, 2 ops)
Зачем: реестр кооперативных стандартов v1 должен покрывать каждый
on-chain процесс из контрактного LEDGER2_PROCESS_REGISTRY — это та самая
карта значимых пользовательских сценариев кооператива, которую видит
председатель. Без черновиков по 9 процессам сайт показывал только 2
пилотных стандарта; теперь у каждого процесса есть YAML-описание со
всеми секциями (паспорт, действия, граф состояний, сценарий, документы,
операции, связи), и любая последующая правка идёт точечно.
Все коды (process_type, ledger_code, wallet_to/from, debit/credit) сверены
с актуальными реестрами в components/cooptypes/src/ledger2/{processes,
operations,wallets,accounts}.ts. Список действий и статусов извлечён из
C++-контрактов (cpp/<contract>/<contract>.hpp + src/). Где документы
имеют известный registry_id (p.sov.axncnv → 51 ConvertToAxonStatement) —
проставлен; для остальных registry_id:0 + TODO для последующей сверки
по cooptypes/cooperative/registry/.
p.mig.trans пропущен по решению: миграция — одноразовый административный
процесс без пользовательского сценария, не относится к набору
стандартов кооператива.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Story-репозиторий findAllByProjectHashesAndIssueHashes делает выборку как
`project_hash IN (...) OR issue_hash IN (...)`. Story, привязанная к задаче,
всё равно имеет project_hash родителя задачи и попадала в результат через
первое условие — даже когда мы сознательно передавали пустой массив issueHashes
(показ задачных требований выключен).
Фикс: пост-фильтр в getStories — при `showIssuesRequirements=false`
выкидываем stories с непустым `issue_hash`. Теперь страницы проекта/компонента
показывают только проектные/компонентные требования, а задачные требования
доступны только через `filter.issue_hash` (страница задачи).
Дефолт `show_issues_requirements=false` (в DTO и сервисе) оставлен — теперь
он действительно работает, а не «делает вид».
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI workflow «Publish Packages» падал на `pnpm install --frozen-lockfile`:
старая pnpm 8 не понимает lockfileVersion 9.0 (lockfile сгенерён локально
pnpm 10.33.0). На последнем production-релизе из-за этого failed check
заблокировал автомерж в main; merge пришлось делать с --admin override,
а сама публикация npm-пакетов из CI не выполнилась.
Прибиваем версию pnpm в action-setup к 10.33.0 — той же, что у user'а
локально. Комментарий обновлён: lockfileVersion 9.0 = pnpm 9/10
(старый комментарий говорил про 6.0/pnpm 8 — это уже не актуально).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Подтянуты три файла из dev: FocusBar.vue, graph/layout.ts, types/standard.ts.
Изменения обратно-совместимы — types и layout одновременно понимают и
reports-схему (scenario.steps + doc.template + doc.step + actions[].role),
и slim-вариант из dev (doc.action + doc.registry_id + actions[].links[]).
Зачем: ветка standards/reports = origin/reports + актуальный UI; YAML-файлы
reports (p.reg.accept, p.wal.depo) рендерятся как раньше, а будущие правки
по стандартам поверх reports получают новые UI-возможности (кнопки-переходы
между стандартами, отдельная колонка «Документы» рядом с действием,
читаемый код документа с приоритетом registry_id над template).
Источник правды по схеме — reports; slim-формат остаётся в dev до
момента, когда reports пойдёт первой в мердж и подомнёт устаревшие
slim-YAML (reg.regist/reg.adduser/reg.recovery/wall.deposit) — это
будет отдельный pre-merge cleanup в dev.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Корневая папка docs-harness/ выглядела чужеродно — все остальные TS/JS-инструменты в репо живут как workspace-пакеты в components/. Перенёс рядом с components/docs/ — есть прямая логическая связь (один производит контент, второй публикует).
- `git mv docs-harness components/docs-harness` — workspace автоматически подхватит, в pnpm-workspace.yaml и lerna.json не трогаем (там уже `components/*`).
- package.json: name `browser-harness` → `@coopenomics/docs-harness`, добавлен `"private": true` (никогда не публиковать), `"type": "module"`, скрипт `pnpm shoot` как алиас orchestrator'у.
- bin/shoot.mjs: REPO_ROOT теперь поднимается на 2 уровня (`HARNESS_ROOT/../..`), не на 1. Финальный вывод печатает относительные пути от корня репо.
- lib/install.mjs: DEFAULT_DOCS_ROOT теперь `HARNESS_ROOT/../docs/docs` (соседний components/docs/docs/), не `../components/docs/docs/`.
- Пути в SKILL.md обновлены (`docs-harness/` → `components/docs-harness/`).
Smoke-тест: `cd components/docs-harness && node bin/shoot.mjs auth/signin` проходит за ~30с — стек/dist/desktop/фикстура/прогон/render все ок, 3 шота снимаются.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Под описанием задачи добавлен expansion-item «Требования к задаче»
с переиспользованным RequirementsListWidget (фильтр {issue_hash}) и
кнопкой «Добавить требование», открывающей CreateRequirementWithEditorDialog
(prefill {project_hash, issue_hash} → backend создаёт story с issue_hash).
- IssuePage.vue: новая секция в обоих layout (mobile/desktop), счётчик в caption,
использует issue.permissions.can_create_requirement.
- RequirementsListWidget.vue: prop permissions расширен union'ом
IProjectPermissions | IIssuePermissions — поля can_edit_requirement /
can_delete_requirement совпадают по форме, виджет не отличает.
- CreateRequirementFabAction.vue, CreateRequirementWithEditorDialog.vue:
filter принимает issue_hash (не только legacy issue_id).
Серверная логика: GetStories по project_hash дефолтно НЕ возвращает stories
с issue_hash (см. предыдущий коммит 89c28b89c1 в controller), поэтому
такие требования видны только на странице задачи и не аккумулируются
на проект/компонент. Matrix-нотификации для них тоже отключены.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Требования (story) с issue_hash перестают аккумулироваться на странице проекта/
компонента и не публикуются в matrix-комнаты — они живут только на странице
самой задачи (DC-фронт добавит виджет следующим коммитом).
- controller/StoryFilterInputDTO.show_issues_requirements: defaultValue true → false
(комментарий: задачные требования не нужны на проекте/компоненте). Фронт-страницы
ProjectRequirementsPage и ComponentRequirementsPage уже передают false явно,
так что новых правок там не требуется.
- controller/generation.service.getStories: дефолт показа issue-stories синхронизирован
с DTO (`=== true` вместо `!== false`).
- controller/generation.service.publishNewStoryToProjectMatrixChats: ранний return,
если у story задан issue_hash — нотификация в проектную matrix-комнату не идёт.
Существующие refs в matrix_requirement_announcement_events продолжают работать
через sync/remove (если кто-то уже публиковал до этого фикса).
- blago-cli/layout.ts: stories с issue_hash раскладываются под
`<base>/issue-requirements/<task-id>-<task-slug>/<story>.md` (отдельная папка
верхнего уровня, чтобы локально не засорять `requirements/` контекст
компонента/проекта).
- blago-cli/run-create-story.ts: фолбэк имени файла под новой схемой.
Миграция: следующий `blago pull` сам перенесёт существующие файлы и подчистит
пустые родители (см. ранее добавленный pruneEmptyParents в sync-entity-file.ts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше orchestrator делал fail-fast если chain/controller/parser лежали — пользователь должен был вручную поднимать стек. Теперь:
- Сервис лежит → `docker compose up -d <name>` + ожидание (60-120с по типу). Если после поднятия всё равно не отвечает — fail с пояснением.
- `node bin/shoot.mjs <scenario> --reboot` → перед всем остальным вызывает `pnpm run reboot:extra` (полный снос blockchain-data + volumes + boot:extra) и удаляет все фикстуры из state/participants/, потому что их WIF на новой цепочке невалидны. Дальше ensureFixture() пересоздаёт пайщика заново.
Использовать --reboot когда сценарий нужен «с нуля» или цепочка/БД разъехались с состоянием. Полный цикл ~3-5 мин.
Разрешения зафиксированы в memory `feedback_infra_permissions.md` (и обновлён SKILL.md): в этом репо я могу самостоятельно `docker compose up -d` и `pnpm run reboot:extra`, пользователь руками не делает.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Один вход для doc-shoot скилла — `node bin/shoot.mjs <scenario>` сам делает pre-flight и автопочинку типовых грабель, чтобы прогон шёл без ручной возни:
- chain/controller/parser — fail-fast с подсказкой docker compose, если что-то лежит
- cooptypes/sdk dist — без них desktop отдаёт пустой спиннер; orchestrator билдит `pnpm --filter ... build`
- desktop dev :2999 — поднимает в фоне если не запущен; рестартует если только что собрали dist (vite-plugin-checker иначе держит кэш «no module» и vue-tsc overlay перехватывает клики Playwright)
- фикстура пайщика — парсит сценарий на ссылку state/participants/<name>.json; если файла нет, лезет в KNOWN_FIXTURES (внутри shoot.mjs) и создаёт через add-plain-participant.ts; неизвестный username → fail с подсказкой добавить профиль
- run.mjs + render-md.mjs (если draft нет)
Намеренно НЕ делает: не поднимает docker, не пишет прозу в draft.md, не вызывает install.mjs (запись в components/docs/ остаётся под ручным контролем).
install.mjs:1 — поправил устаревший комментарий про /home/admin/mono-ai-2 (фактический дефолт давно относительный — соседняя components/docs/docs/ от harness).
SKILL.md в ~/.claude/skills/doc-shoot/ обновлён отдельно (вне репо): убран mono-ai-2, поправлено что signin это пайщик а не председатель, основной поток теперь через bin/shoot.mjs.
Проверено: orchestrator проходит на текущем сценарии auth/signin за ~30с, все 3 скриншота снимаются, draft.md генерируется.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stories с issue_hash раскладываются под `<base>/requirements/<task-prefix>-<task-slug>/`
(вместо прежнего `<base>/issues/.../...-requirements/`). Так все требования —
и привязанные к компоненту/проекту, и привязанные к задаче — сидят в одной папке
`requirements/`, а `issues/` хранит только файлы задач.
- layout.ts: новый путь для stories с issue_hash.
- run-create-story.ts: фолбэк имени файла под новой схемой.
- sync-entity-file.ts: после rename подчищаем пустые родительские каталоги,
чтобы старые `<base>/issues/<task>-requirements/` не оставались артефактом.
- workspace-index.ts: для stories с issue_hash в INDEX.md выводим
«(задача <id> в <компонент>)» вместо «(проект/компонент <id>)»; задачи
попадают в карту id ровно для этой подписи (в самом INDEX.md задач нет).
Ручной миграции не требуется: на ближайшем `blago pull` syncEntityFile увидит
новый канонический путь и переместит файл, зачистив пустую старую папку.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Без merge'а старой ветки 989-4 (там 47/48 коммитов — устаревшие версии ledger2/reports работы, уже сделанной заново в 989-1/2/3 и доехавшей до reports). Принесена только инфраструктура для съёмки документации:
- docs-harness/ — playwright-сценарии + хелперы (lib/{harness,annotate,install,render-md}.mjs), run.mjs, package.json, README, scenarios/auth/signin.mjs
- components/boot/src/scripts/add-plain-participant.ts — генератор фикстур пайщиков для harness
- components/docs/docs/new/auth/signin.md + 3 PNG в assets/new/auth/signin/ — эталонная инструкция «Вход пайщика»
- components/docs/mkdocs.yml — добавлена секция «Вход в систему» в nav (без удаления ссылки «Реестр стандартов» — это посторонний шум 989-4)
Проверено: npm install проходит, синтаксис всех .mjs чистый, run.mjs стартует и доходит до загрузки сценария — дальше нужны живые chain/parser/controller/desktop и фикстура ivanpetrov (генерируется add-plain-participant).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раздел «Задачи» создавал слишком много шума — задачи обычно ищут с привязкой к
проекту/компоненту, а сам проект/компонент находится по этому индексу. Оставлены
проекты, компоненты и требования.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Чтобы безопасный паттерн (создание/удаление дочерней issue/story) работал из коробки:
- runCreateIssue, runCreateStory, runDelete после успешной мутации запрашивают GetProject
и через refreshParentProjectVersion обновляют remote_updated_at родителя в index.json
(если файл «грязный» — только индекс, иначе ещё и переписывают файл свежим контентом).
Если на сервере между нашими операциями содержимое реально менялось извне — индекс не
трогаем, чтобы push выявил настоящий конфликт.
- При pull/push/create/del/restore/clean генерируется корневой INDEX.md рабочей копии
с проектами, компонентами, требованиями и задачами и относительными путями — для
быстрой адресации без обхода дерева. Полные UUID требований сокращаются до 8 символов.
INDEX.md добавлен в .blagoignore по умолчанию.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Поля «С даты» / «По дату» больше не readonly — можно ввести 2026-04-01 руками.
mask `####-##-##` оставлен для формата. reload запускается только при полной
дате (10 символов), чтобы не мигать индикатором загрузки на каждой цифре.
Иконка event как и раньше открывает q-popup-proxy с q-date.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Председатель больше не может откатывать операцию через UI: action `ledger2::revert`
сохранён, но принимает только подпись от whitelisted-контрактов
(`check_auth_and_get_payer_or_fail(contracts_whitelist)` без ветки `has_auth(coopname)`).
Why: ledger2 — учётный слой; зеркальная проводка председателем top-level
рассинхронизирует state контрактов-инициаторов (registrator/wallet/capital).
Контракты-инициаторы знают свой operation_code и могут собрать корректные
параметры зеркала из cooptypes/operations.hpp; они же одновременно откатывают
свои домены (participants/deposits/contributors).
Контракт ledger2:
- revert.cpp: убрана ветка has_auth(coopname); top-level от пайщика/председателя
падает на whitelist-check. Все остальные проверки (запрет o.mig.*, валидация
mirror-параметров) сохранены.
Backend (controller):
- удалены: revertOperation mutation, RevertOperationInputDTO, метод service
revertOperation + computeMirrorParams + WALLET_OP_CODE map, RevertBlockchainDomainInterface
и адаптер revert(), Ledger2StatePort.getOperationByGlobalSequence + repo-метод
- осталось: walmove (с UI), порт revert на cooptypes для контрактных потребителей
SDK:
- удалена mutation/ledger2/revertOperation.ts; cooptypes Actions.Revert + IRevert
оставлены (нужны другим контрактам).
UI (desktop):
- удалён RevertOperationDialog.vue
- из OperationsPage убраны: import RevertOperationDialog, useSession/storeToRefs,
revertDialog state, canRevert/openRevertFor/onRevertSuccess, кнопка ↩
- Ledger2 store/api/types: убран revertOperation method/IRevertOperationInput
Тесты:
- удалены revert.test.ts + adjust/revert.ts helper
- walmove.test.ts (3 теста) + остальной suite — зелёный
Полный suite: 92 passed | 1 skipped | 0 failed (clean reboot).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OperationsPage:
- по умолчанию actionNames=[apply,walmove,revert] (раньше только apply,
и корректировки появлялись только с включённым «Только корректировки»)
- поле «ID процесса (process_hash)» в шапке фильтров: Enter / клик-иконка
применяет точный hash (lower-case), очистка сбрасывает фильтр
- processHashInput синхронизируется с route.query.process_hash при mount
WalletTransferDialog / RevertOperationDialog:
- закрытие после успеха через прямой emit('update:modelValue', false)
до finally — раньше close() блокировал guard'ом на loading=true,
поэтому диалог не закрывался после успешной транзакции
- SuccessAlert больше не показывает кусочек process_hash (его всегда
можно посмотреть в реестре через новый поиск)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Случайно вставил Dialog внутрь q-table body, разорвав expanded q-tr
(`Inconsistent indentation. Expecting either 2 or 8 spaces/tabs` на
template(#item)). Перенёс на уровень div.page-shell — рядом с q-card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Все 90 тестов теперь зелёные (89 passed | 1 skipped | 0 failed).
Изменения:
* boot/src/init/infra.ts: 2s → 8s sleep между setContract и createToken.
На свежем nodeos setabi последнего контракта не успевал коммититься,
eosjs.getAbi('eosio.token') падал с "Read past end of buffer".
* boot/src/tests/registrator/registerUser.ts: legacy fund::coopwallet
(circulating_account/initial_account) → ledger2::accounts (счета 80, 86).
После Epic 1 confirmreg перестал вызывать Ledger::add, поэтому
legacy-баланс не пополняется. Теперь сравниваем приращение balance счёта
80 (Паевой фонд, на который попадает minimum через PUT_MINSHARE) и
счёта 86 (Целевое финансирование, через PAY_ENTRANCE).
* boot/src/tests/capital-import.test.ts: ожидаемый contributor.status
скорректирован 'active' → 'import'. ImportContributor создаёт запись
со статусом IMPORT (см. Status::IMPORT в contributors.hpp); перевод
в ACTIVE — отдельным шагом.
* boot/src/tests/ledger2-read-layer.test.ts: фильтр accountId без
actionNames возвращает все sibling-actions процесса (по дизайну
репозитория). Тест явно передаёт actionNames=['debit','credit'],
чтобы проверить только прямые проводки.
* boot/src/tests/capital.test.ts: regshare-идемпотентность — добавлен
sleep(2000) перед повторным regshare, чтобы expiration транзакции
отличался (иначе nodeos отклоняет как duplicate transaction).
* controller/schema.gql + sdk/zeus: регенерированы из ProcessRegistry
и Ledger2-резолверов (operationCode/operationCodes вместо action*).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GraphQL HISTORY_QUERY: actionCode → operationCode; фильтр actionCodes →
operationCodes; ожидаемый код в filter-тесте mig.share → o.mig.share.
Регрессии после operations/processes refactor закрыты:
17 failed → 4 failed (84 → 86 passed). Оставшиеся 4 — pre-existing,
не связаны с рефакторингом нейминга:
- capital-import: contributor.status='import' вместо 'active'
(поведение capital-контракта, не наша область).
- registrator x2: compareTokenAmounts на legacy
fund::coopwallet.circulating_account.available, который не пополняется
с Epic 1 (Ledger::add удалён).
- ledger2-read-layer: фильтр accountId возвращает sibling-actions
одного процесса (включая wallet_to=80000), тест ожидает строгий 51000.
Pre-existing разрыв между документированным поведением фильтра
(через process_hash IN ...) и ожиданиями теста.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Файлы scripts/out/*.json — локальные выгрузки off-chain аудита,
случайно попавшие в предыдущий коммит. Возвращаю tree к чистому состоянию,
директория добавлена в .gitignore.
* Грузим IAccount через accountStore.getAccounts (по паттерну ListOfParticipantsPage),
чтобы получить private_account с ФИО. Таблица participants из контракта давала
только username — ФИО оттуда не извлечь.
* В колонке «Пайщик» сверху — ФИО (getName), под ним мелким моноспейсом — username.
* Переписал кастомный <table> на q-table с динамическими колонками (по одной на
активную программу) — теперь визуально согласован с AccountsPage/CoopWalletsPage.
Сортировка работает по суммарной (available + blocked) ячейке.
* API-слой сузил до loadProgramsAndWallets(): programs + progwallets, без участников
(их даёт accountStore).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* WalletsPage — shell по паттерну DocumentsPage: useHeaderActions + RouteMenuButton,
два child-route reports-wallets-coop / reports-wallets-participants (redirect на coop).
* Переименовал *Tab.vue → *Page.vue (теперь это полноценные страницы, не табы).
* В ячейках матрицы убрал JetBrains Mono и text-weight 500 — нормальный шрифт, bold только в Итого/Σ,
размер 14px, иконки приглушены opacity 0.75. Читается как суммы на странице счетов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalletsPage стала shell с q-tabs:
• «Кооператив» — прежний список ledger2-кошельков (вынесен как есть в
CoopWalletsTab.vue).
• «Пайщики» — новый срез soviet.progwallets: матрица «пайщик × программа»,
загружается напрямую через fetchTable (не через GraphQL, т.к. на
странице нужны текущие blockchain-данные, а не postgres-индекс).
ParticipantWalletsTab:
• Таблица: строки = accepted-пайщики (отсортированы по username);
колонки = активные программы коопа (Цифр.Кошелёк, Благорост,
Генератор, membership-программы и т.д.); ячейки — available сверху
(зелёная монета) и blocked снизу (коричневый замок), нули бледнее.
Итог на строке + строка Σ по программе внизу.
• Членские взносы не показываем (per user feedback).
• Ссылка «К операциям пайщика» справа → reports-operations?username=X.
OperationsPage: поддержка ?username=X — новый чип-фильтр «Пайщик %FIO%»
с clearUsernameFilter + передача в ILedger2HistoryFilterInput.username.
Сервер (GetLedger2HistoryInput) уже поддерживает поле username — добавлен
только клиентский path.
participant-wallets-api.ts — локальная api-функция loadParticipantWalletsMatrix,
читает scope=coopname из soviet.programs + .progwallets + .participants
и строит pivot-срез клиентски (под 6 коопов с ≤60 progwallets — в самый
раз, без пагинации).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Терминология XML/PDF пустая для бухгалтера. По назначению:
XML → в ФНС/СФР («для отправки»)
PDF → распечатать/подписать/подшить («для просмотра»)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Баг: в 2026 году клик на БУХОТЧ открывал форму «Отчёт за 2026», хотя
председатель в апреле 2026 должен сдавать отчётность ЗА 2025. То же для
Q4 кварталок (NDFL6/RSV/FSS4) и декабря ПСВ — при dueYearOffset=1 в
январе/феврале/марте 2026 календарь относил ячейку к 2026, а не 2025.
Было: `year` календаря = reportYear (год ЗА который). dueYearOffset
учитывался только в calcDueDate, но в UI ячейка оседала в том же году.
Клик → форма получала year=calendarYear → всегда «за текущий».
Стало: `year` календаря = displayYear = календарный год СДАЧИ.
Для каждой ячейки reportYear = displayYear - entry.dueYearOffset.
- БУХОТЧ март 2026 → reportYear=2025 (годовая сдача)
- Q4 (NDFL6/RSV/FSS4) февр/январь 2026 → reportYear=2025
- ПСВ январь 2026 (декабрь prev) → reportYear=2025
- Q1..Q3 кварталок + ПСВ фев..дек → reportYear=displayYear (без сдвига)
DTO ReportCalendarPeriodEntry: добавлено поле reportYear (Int!). Фронт
эмитит entry.reportYear — форма открывается за правильный период.
Backend: archive/drafts/marks читаются за displayYear-1 и displayYear,
ключ (reportType, period, year) чтобы не смешивать статусы 2025 vs 2026
в одной ячейке. calcDueDate как было — принимает reportYear, применяет
offset к году для реальной даты.
Schema.gql / zeus — регенерированы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Аудит после бага ЕФС-1 Q4 → '0': проверил все XSD-схемы на enum-ограничения
и сверил с hard-code в генераторах. Других ошибок маппинга нет, но XSD-тест
гонялся только на один период в каждом генераторе — если завтра кто-то
поменяет `switch (quarter)` обратно на '0', баг снова всплывёт в проде.
Теперь XSD-валидация iterates по всем реально допустимым периодам:
- NDFL6 it.each Q1..Q4 (enum 21/31/33/34)
- RSV it.each Q1..Q4 (enum 21/31/33/34)
- PSV it.each M1..M12 (enum 01..12)
- UV_VZNOSY it.each M1..M12 (enum 21/31/33/34 + НомерМесКварт 11..13)
- UUSN it.each Q1..Q4 (enum 21/31/33/34 + НомерМесКварт 01..04)
- FSS4 уже покрыт Q1..Q4 (предыдущий коммит)
BUHOTCH/DUSN — yearly, hardcode Период='91'/'34', enum XSD {34,91..94}/{34,50,95,96} —
оба в enum, покрыты существующим single-path тестом.
Тестов: 71 → 102 (+31 XSD-прогона).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Баг: при скачивании ЕФС-1 за Q4 попадала ошибка XSD
«The value '0' is not an element of the set {'03','06','09','12'}».
XSD efs1.xsd требует код = месяц окончания квартала: 03|06|09|12.
Автор считал «год (IV) = 0», но ЕФС-1 не бывает годовым — Q4 → декабрь → «12».
Тесты:
- Старый «квартальные коды СФР: 03/06/09/0» фиксировал баг — переписан на
«03/06/09/12».
- XSD-валидация была только на Q1. Теперь — it.each по всем 4 кварталам.
У RSV другая система (ФНС-коды 21/31/33/34), не пересекается. PSV — месячная,
собственная мапа 1..12. Затронут только Fss4Generator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SettingsPage:
- Убрали автосохранение — сервер дёргаем только по клику «Сохранить реквизиты».
- Обернули всё в <q-form greedy>: валидация всех полей перед @submit. Если
хоть одно красное — @validation-error, save() не вызывается, ошибки летят
только локально, никакого server-5xx из-за неполного ввода.
- Кнопка сохранить живёт в sticky save-bar внизу страницы (не прыгает в шапку).
Рядом компактный статус: «Реквизиты сохранены» / «Ошибка сохранения».
- Placeholder'ы — правила ввода, не примеры: «1–3 цифры» вместо «16»,
«5 цифр» вместо «20200», «XX.XX или XX.XX.XX» для ОКВЭД.
RequisiteField:
- Для фикс-длинных цифровых полей используем Quasar-mask (`#` = только цифра,
буквы блокируются на keypress нативно, в поле вообще не появляются).
- Для ОКВЭД (digits + точки, переменная длина) — @keydown-блокер + @paste-
блокер: буквы не попадают в поле даже на мс.
- Убрали digitsOnly-проп — его задачу теперь решает mask.
- Добавили pattern + patternMessage для СНИЛС/СФР (частичный ввод с мaskой
не даёт сохраниться).
Масks/правила по полям:
ОКВЭД digits+dots, maxLen 8, паттерн XX.XX(.XX)?
ОКФС mask ### (1–3 цифр)
ОКОПФ mask ##### (ровно 5)
ОКТМО mask ###########, exactLengths [8, 11]
ОКПО mask ##########, exactLengths [8, 10]
СНИЛС mask ###-###-### ##, pattern \d3-\d3-\d3 \d2
СФР mask ###-###-######, pattern \d3-\d3-\d6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RequisiteField:
- Новые props: digits-only, digits-dots-only, max-length, exact-lengths (ИЛИ),
pattern + pattern-message. Фильтр на input режет недопустимые символы до
эмита, maxlength режет длину. lazy-rules — валидация только после первой
потери фокуса (не атакуем пользователя красным пока он печатает).
- Форвардит @blur наверх — для savetrigger родителя.
- Нативный required по rules, без отдельного :error-prop.
SettingsPage:
- Автосохранение теперь по blur поля (600ms coalescing debounce на случай
быстрого tab-переключения), не по input. Раньше сохраняло пока пользователь
ещё печатал → ловил «ОКТМО должен быть 8 или 11 цифр» на частично
набранном «123».
- Классификаторы: ОКВЭД (digits+dots, pattern 94.99/46.73.7), ОКФС (1–3 digits),
ОКОПФ (mask #####), ОКТМО (8|11 digits), ОКПО (8|10 digits). СНИЛС/СФР —
маски оставлены. Должность председателя — maxLength=100, репдок — maxLength=200.
ReportEditorDialog (stub readiness):
- q-list с длинным reason для каждого поля заменён на компактный ряд
q-chip-ов с label. Reason-ы съедали воздух и повторяли одно и то же
для 5 классификаторов — теперь один заголовок + 5 чипов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocumentsPage/ReportEditorDialog:
- Перед рендером формы вызываем checkReportReadiness(reportType). Если ready=false —
показываем stub «Сначала заполните реквизиты» со списком недостающих полей и
кнопкой «Перейти к реквизитам» (navigate → reports-settings?focus=<key>).
- Форма/черновик/кнопки Скачать XML/PDF не грузятся до заполнения реквизитов
(раньше XML скачивался даже при пустом СНИЛС подписанта). Отметки «не надо
сдавать» / «сдан вне платформы» доступны независимо от gate.
- Поправили tooltip «Перегенерировать» — убрали упоминание блокчейна (данные
хранятся в БД).
SettingsPage:
- Убрали кнопку «Сохранить реквизиты» сверху — автосохранение через debounce
(600ms) по любому изменению поля/signerType. Статус «Сохраняется…/Сохранено/
Ошибка» — маленький чип в заголовке карточки. onBeforeUnmount дописывает
последнюю правку синхронно, если таймер ещё не сработал.
- Убрали подписи «Данные из блокчейна (read-only)» и «Ручной ввод» — данные
в БД, user-speak. Заголовки карточек сохранили.
RequisiteField:
- Убрали цветные бейджи Блокчейн / Ручной ввод / Не заполнено. Вместо
«Не заполнено»-бейджа — нативная q-input-валидация: пустое required-поле
подсвечивается красным outline + error-message «Обязательное поле».
Звёздочка * в label отмечает обязательные поля.
- Read-only поля из БД — через :readonly+:disable, без отдельного бейджа.
OperationsPage:
- Сумма в таблице — bold + text-grey-10 для контраста. В мобильном grid-item
добавили блок «Сумма» с text-body1.text-weight-bold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zeus-скаляр ID разворачивается как непрозрачный `{}`, поэтому `:id='payment.id'`
не присваивался string-пропу кнопок. Явный `String(payment.id)` убирает обе ошибки
vue-tsc без изменения runtime-поведения (v-if уже гарантирует truthy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После мёржа graph-standarts → dev реестры ledger2 живут в
components/cooptypes/src/ledger2/{accounts,wallets}.ts. Desktop-копии
становятся тонкими re-export-обёртками, публичный API путь
src/shared/lib/ledger2 сохранён (AccountIdCell / WalletIdCell не трогаем).
- account-registry.ts: локальный массив и 4 хелпера заменены на re-export
Ledger2.{LEDGER2_ACCOUNT_REGISTRY,getAccountName,getAccountMeta,storedIdToCode}.
- wallet-registry.ts: аналогично для LEDGER2_WALLET_REGISTRY и getWalletName.
- Типы AccountKind/AccountMeta/WalletMeta — re-export из Ledger2-namespace.
- Контент реестров в cooptypes один-в-один совпадает с тем, что было в
desktop (проверено diff'ом). При добавлении счёта/кошелька теперь
править только cooptypes + contract, второй поток (desktop) подхватит
автоматически.
Попутно zeus-файлы (controller/zeus + sdk/src/zeus) регенерированы после
rebase на origin/dev — никаких semantic-изменений, только нормализация
порядка экспортов.
Закрывает blago issue 989-9 (подтянуть cooptypes с ledger2-реестрами).
После рефактора генераторов в Sprint 1/2 (на BuhotchEditsShape /
ZeroReportEditsShape) старые фикстуры передавали legacy ReportInput →
все 45 тестов падали на undefined 'header.idFile'. Ранее пометил
describe.skip'ом, сейчас переписал честно.
- Две фикстуры ПК «Ромашка» вместо legacy baseInput:
- buhotchBaseEdits: BuhotchEditsShape (с explicit header/organization/
signer/balance/notes). Балансы по умолчанию нули; тест «выплёвывает
суммы в СумОтч/СумПрдщ/СумПрдшв» сам подставляет конкретные числа.
- zeroBaseEdits: ZeroReportEditsShape для всех 7 нулёвок, + helpers
withPeriod(period) / withYear(year) для per-test оверрайдов.
- Тест «считает балансы из ledger в тыс.₽» заменён на «выплёвывает
edits.balance.* дословно». Ledger→balance-конвертация — ответственность
ReportEditsBuilderService, не генератора (разделение слоёв).
- Тест имени файла теперь «эхом возвращает header.idFile в fileName и
атрибут ИдФайл». Формат имени строит builder; генератор — просто эхо.
- Тест «без sfrRegNumber» для ЕФС-1: передаём signer.sfrRegNumber=null
вместо удаления поля.
- ReportRegistryService.getAll() → getAvailableReports() (актуальное API).
- import ReportInput удалён.
Результат: 68 passed / 0 failed / 0 skipped. XSD-валидация для всех 5
MVP-форм + 2 legacy снова в CI-гейте. Сверка с ПК «Ромашка» эталонами
(6 тестов) — работает.
Оставшиеся задачи из Sprint 4 debt (кроме E2E — это отдельная инфра-работа).
- ReportEditorDialog: кнопка «Скачать PDF» теперь работает для всех 5 MVP-форм
(BUHOTCH/NDFL6/RSV/PSV/FSS4). Paper-view компоненты уже имели идентичный
контракт `{xml, requisites?, year?}` и `.printable-form`-root — просто
подключил Ndfl6Form/RsvForm/PsvForm/Efs1Form к `.hidden-pdf-source` по
`v-else-if` на reportType. Добавлен computed `hasPdfPaperView` +
PDF_SUPPORTED_TYPES как единый источник правды.
- reports-calendar-registry: RU_HOLIDAYS_BY_YEAR расширен на 2028–2029
(срок БУХОТЧ за 2026 сдаётся 31.03.2027; при планировании за 2027 —
31.03.2028). Захардкоженные даты вынесены в общий BASE_RU_HOLIDAYS.
- widgets/report-forms/README обновлён: снято упоминание Sprint 3 debt.
Blago-server sync — подтверждено актуальным (pull завершён, нет расхождений).
Остаётся в debt только STORY-3-4 E2E happy-path — нет Playwright-инфры
под reports-экстеншн, отдельный заход с запущенным стендом.
- ReportEditorDialog.action-panel на <md экранах становится slide-in overlay
справа (width: min(85vw, 320px)), открывается кнопкой-слайдером в q-bar,
закрывается backdrop-тапом или кнопкой «Скрыть» в панели. На ≥md — как
было, inline-колонкой справа. Переход между режимами на ресайзе автоматом.
Raison d'être: панель 260px на mobile перекрывала форму, редактировать
было невозможно.
- ReportsCalendar: 5×12 матрица в .calendar-scroll-обёртке с overflow-x:auto,
grid имеет min-width:900px и min-col 54px — ячейки не прессуются, пользователь
свайпает горизонтально. Раньше грид ломал вёрстку на узких экранах.
Bug: q-checkbox «Достоверность подтверждена аудитором»/«Утверждено общим
собранием» и q-option-group «Подписант/Уполномоченный представитель»
визуально не переключались (управляемые Quasar-контролы ждут реальной
смены :model-value). q-input-поля казались рабочими, т.к. нативный input
сам показывает набранный символ до синка с моделью.
Причина — паттерн :edits + @update:edits + structuredClone(reactive-proxy)
в BuhotchEditor/ZeroReportEditor давал сбой: у Vue-proxy есть служебные
traps (__v_raw и т.п.), structuredClone на нём отрабатывал неочевидно,
эмит не пробрасывал новый объект как нужно.
- BuhotchEditor & ZeroReportEditor: defineModel<TEdits>('edits') (Vue 3.4+)
вместо prop+emit. Внутри — JSON.parse(JSON.stringify(current)) вместо
structuredClone — чисто POJO, без риска proxy-трапов.
- ReportEditorDialog: v-model:edits на оба редактора + writable computed
с get/set, пишущим в общий useReportDraft.edits (один source of truth).
Убраны лишние onBuhotchEditsUpdate/onZeroEditsUpdate handler'ы.
- BUHOTCH-лейблы приведены к XSD (NO_BOUPR_1_159_00_05_04_01):
- «Достоверность подтверждена аудитором» → «Подлежит обязательному
аудиту» (атрибут ПрАудит, обычно 0 для кооперативов).
- «Утверждено общим собранием» → «Подлежит утверждению общим
собранием» (атрибут ПрУтвер, =1 для потребкооперативов по ст.38 ФЗ-193).
- Оба с тултипом-подсказкой для бухгалтера.
- ReportsCalendar: defineExpose({ reload }). DocumentsCalendarPage держит
template-ref на виджет и дёргает reload() в onMarked/onGenerated.
Раньше reportStore.loadCalendar() возвращал свежие данные, но не пушил
их в widget.rows — статус ячейки обновлялся только при ре-маунте страницы.
- psvEntries: 12 месяцев вместо 8. ПСВ — ежемесячная по ст.431 НК РФ;
послабление ФНС про м3/м6/м9/м12 (закрываются РСВ) не отсутствие формы,
а опциональное правило — пользователь сам отмечает «Не надо сдавать»
на нужных ячейках. Декабрь: dueYearOffset=1 (срок 25.01 следующего года).
Backend (3-1):
- reports-calendar-registry.ts — хардкод сроков 5 MVP-форм:
БУХОТЧ 31.03 след.года, 6-НДФЛ/РСВ/ЕФС-1 квартальные до 25-го,
ПСВ ежемесячно кроме последнего месяца квартала.
- ReportCalendarRowDTO + CalendarEntryStatus enum (submitted/draft/
overdue/empty).
- ReportCalendarResolver.getReportCalendar(year) — параллельно тянет
архив и драфты на год, матрицит статусы.
SDK:
- reportCalendarSelector + queries/reports/getReportCalendar.ts.
Frontend (3-2 / 3-3):
- widgets/reports-calendar/ReportsCalendar.vue — матрица 5×12 с
навигацией ± год, tooltip'ами и кликом по ячейке →
@select(reportType, year, period) → открытие ReportEditorDialog.
- CalendarCell.vue — одна ячейка (status-цвета, иконки, тултип).
- DocumentsPage: q-tabs (Календарь / Список форм / Архив), календарь по
дефолту. ReportEditorDialog открывается из обеих вкладок + из календаря.
Docs / lint (3-5):
- widgets/report-forms/README.md обновлён (editable + paper-view split).
- widgets/reports-calendar/README.md — архитектура календаря + известные
ограничения (переносы на выходные — в долг).
- Почищены unused imports в report.dto.ts, non-null в fss4.generator.ts.
Lint по reports extension и desktop/reports/ — чист.
STORY-3-4 (E2E happy-path) оставляем как долг — нужен отдельный сетап
playwright-сценария с реальным контроллером и фикстурой юзера-chairman.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В предыдущем коммите 172f0f2 schema.gql обнулилась из-за того, что
generate-schema завершался EACCES на попытке записи в
logs/application-2026-04-23.log (файл создан ранее root-процессом из
контейнера). Nest-logger ронял процесс до финальной записи schema.
Сейчас лог удалён, generate-schema отработал до конца —
schema.gql = 12511 строк, 225 типов, включая ZeroReportEdits*,
FieldError, BuildInitialReportEdits и validateReportEdits/
generateReportFromEdits мутации.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SDK (STORY-2-2):
- selectors/reports/patterns.ts — regex-константы и reportRules helper
(inn/innUl/kpp/ogrn/okved/oktmo/okfs/okopf/okpo/snils/sfrRegNumber/
dateDdMmYyyy/kbk + length/optionalRegex). Единый источник для форм.
- selectors/reports/fieldErrorSelector.ts — селектор { path, message }.
- queries/reports/validateReportEdits.ts — SDK wrapper.
Backend (STORY-2-1):
- FieldErrorDTO + резолвер validateReportEdits(reportType, editsJson).
- Валидация через class-validator + plainToInstance → BuhotchEditsInputDTO.
- flattenValidationErrors: ValidationError[] → FieldError[] с точечным
JSONPath (organization.inn, balance.assetsTotal.otch). Совпадает с
editedFields, которые трекает клиент — прямой matching для подсветки.
Frontend (STORY-2-3):
- useReportDraft: fieldErrors Record<path, string[]>, isValid, validateNow
с debounce 500мс после каждого markDirty. При правке поля — локально
сбрасываем его ошибки (не ждём сервер).
- BuhotchEditor: новый prop fieldErrors, helpers errFor/msgFor; каждое
q-input получает :error / :error-message по JSONPath.
- ReportEditorDialog: валидационный бейдж в action-панели (ok/bad),
счётчик ошибок; скачивание XML/PDF блокируется если не isValid.
- Все rules в BuhotchEditor переехали на reportRules.* — regex больше
не хардкодится, единый источник истины = SDK patterns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocumentsPage монтирует диалог через v-if='showEditor' — на момент setup()
props.modelValue уже true, классический watcher false→true не срабатывает
ни разу. load() не вызывается, edits остаются null, BuhotchEditor не
рендерится (v-if='edits'). Виден пустой экран без ошибок.
Решение — immediate: true: watcher отрабатывает на mount, проверяет что
modelValue=true и reportType=BUHOTCH, запускает load + loadRequisites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Фикс к 791f269d: в прошлом коммите BuhotchEditor был написан, но не подключён —
пользователь по-прежнему видел старый read-only flow. Теперь:
- DocumentsPage: openEditor(r) → открывается ReportEditorDialog.
Промежуточный ввод реквизитов/corrections больше не нужен (данные уже
в requisites + ledger2, редактируются внутри формы).
- ReportEditorDialog — фулскрин, слева BuhotchEditor (editable, q-input
с rules по XSD), справа панель: Скачать XML, Скачать PDF, Перегенерировать
(dirty-merge), Удалить черновик, статус autosave.
- useReportDraft загружает edits через buildInitialReportEdits, autosave
500мс, dirty-tracking JSONPath. PDF-экспорт — через скрытый paper-view
BuhotchForm.vue + html2pdf.
- Для не-BUHOTCH форм показываем заглушку «Sprint 2».
- Удалены GenerateReportDialog.vue + ReportPreviewDialog.vue — оба пути
поглощены ReportEditorDialog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Эпик 989-9 «Редактируемые inline-формы отчётов + календарь» — конец Sprint 1
(end-to-end путь на BuhotchForm: draft → dirty-merge → редактор).
Backend:
- ReportDraftEntity + CRUD-резолверы save/get/list/delete (uq по owner+type+year+period).
- BuhotchEditsDTO — зеркало XML БУХОТЧ с regex-паттернами из XSD; общий
domain/patterns.ts с ИНН/КПП/ОГРН/ОКТМО/ОКВЭД/ОКФС/ОКОПФ/ОКПО/СНИЛС/СФР.
- ReportEditsBuilderService.build() → BuhotchEditsShape: дефолты из
ledger2 + requisites + balance_corrections.
- buildInitialReportEdits резолвер с dirty-merge: defaults ⊕ editedFields
из существующего drafта (applyDirtyOverrides по JSONPath).
- Рефактор BuhotchGenerator.generate(edits: BuhotchEditsShape) — чистая
сериализация без ledger-логики (она теперь в EditsBuilder).
- generateReportFromEdits резолвер + XSD-валидация + запись в архив.
- Старый generateReport(data, organization) удалён полностью.
ReportInput-shape оставлен временно для 6 non-BUHOTCH генераторов
(переведутся на свои EditsShape в STORY-2-5/2-6). BUHOTCH-тесты
генератора skip'нуты до переписи на edits.
Frontend:
- useReportDraft composable: load через buildInitialReportEdits,
debounced autosave 500мс, dirty-tracking JSONPath'ами, regenerate/clear.
- BuhotchEditor.vue + BalanceRowEditor.vue — editable reference-форма:
v-model:edits, @dirty, q-input rules по XSD-regex. Старый BuhotchForm.vue
оставлен для post-generation XML-preview (заменится в STORY-2-4).
- reportStore: старый generate(data, org) удалён → новые методы
buildInitialEdits / getDraft / saveDraft / deleteDraft /
generateFromEdits; DocumentsPage временно использует fast-path
buildInitialEdits → generateFromEdits до STORY-1-7/STORY-2-4.
SDK:
- generateReport.ts удалён.
- Новые мутации: generateReportFromEdits, saveReportDraft, deleteReportDraft.
- Новые queries: buildInitialReportEdits, getReportDraft, listReportDrafts.
- Новые selectors: reportDraftSelector, buildInitialReportEditsSelector.
- schema.gql + Zeus регенерированы (52 класса резолверов).
bmad:
- components/context/bmad/reports-editable-forms/ — config + sprint-status.yaml
c 17 сторями по 3 спринтам; Sprint 1 закрыт.
Typecheck + vue-tsc чисты. XSD-тесты генераторов (кроме BUHOTCH) остаются
актуальными через legacy ReportInput path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XML генераторов ФНС объявлял encoding="windows-1251", но Blob в браузере
писался utf-8. Контур/СБИС выдавали предупреждение «объявлена windows-1251,
фактическая UTF-8» при загрузке файла.
Теперь перед скачиванием проверяем пролог: если cp1251 — перекодируем
JS-строку в байты cp1251 через пакет `windows-1251`. Для СФР-ЕФС-1 (utf-8)
оставляем как есть. Байты теперь физически совпадают с объявленной
кодировкой.
После merge origin/dev подтянулся фикс генератора схемы (0c5e04a484),
который возвращает все 51 классы резолверов (было 8) и поле
can_edit_requirement в permissions.
Пересобран schema.gql явным `generate-schema`, далее `generate-client`
+ `sdk build`. Типы в SDK соответствуют runtime controller, tsc чист
и в desktop, и в controller.
Бухгалтеру нужен заполненный документ для просмотра, а не пустой бланк.
Раньше кнопка «PDF» скачивала печатную форму без данных — бесполезно;
XSD вообще не нужен ни председателю, ни бухгалтеру.
Стало:
- html2pdf.js конвертирует отрендеренную `.printable-form` (Vue) в PDF на
клиенте. Две кнопки в диалоге: «Для отправки (XML)» + «Для просмотра
(PDF заполненный)»
- Имя файла: <тип>-<год>[-q<период>].pdf
Удалено мёртвое:
- Backend: ReportStandardsService + downloadReportXsd/BlankPdf резолверы +
DTO ReportXsdFile/ReportBlankFile
- SDK: queries/selectors для тех же
- Frontend: downloadXsd/downloadBlankPdf из store/api/types
- Бланки в components/reports-standarts/ остались как референс
разработчика формы — переформулировано в README
UI-правки:
- Убрана кнопка «Скачать XSD» — бухгалтер/председатель её не используют
- Две кнопки: «Для отправки (XML)» и «Для просмотра (PDF)»
- Удалён метод downloadXsd из Report store (api/types остались — резолвер
на бэке может пригодиться для QA)
Инфраструктура форм:
- useReportXml.ts — общий composable: парсер XML через DOMParser +
базовая шапка + helper'ы (getAttr, getNum, getByLocal для namespace-
тегов ЕФС-1, padInn, formatDate, fmt/fmtZero)
- _printable-form.scss — общий стиль A4-бланка: Times New Roman, рамки,
штрихкод-заглушка, ячейки ИНН, .data-table, подпись. Подключается через
@use в scoped-стиль каждого SFC
- Удалён FormStub.vue — больше не нужен, все формы полноценные
Полноценная вёрстка:
- BuhotchForm — рефакторинг на useReportXml + shared scss
- DusnForm — титул + раздел 1.1 (сумма к уплате) + 2.1.1 (расчёт по
объекту «доходы») из <УСН> в XML
- Ndfl6Form — титул + раздел 1 (КБК, сроки 1-6) + раздел 2 (ставка,
исчислено/удержано/возвращено)
- RsvForm — титул + раздел 1 (сводка ОПС/ОМС/ВНиМ). Для нулевого отчёта
блок РасчетСВ пуст, явно показываем это пометкой
- PsvForm — титул + раздел 3 с таблицей персон (ФИО/СНИЛС/сумма выплат)
- UusnForm — единый лист с атрибутами <УвИсчСумНалог> + маппинг кодов
периода (21/31/33/34 → название квартала)
- Efs1Form — раздел 2 СФР с подразделами 2.1 (тариф, база, исчислено) и
2.3 (СОУТ). Парсит namespace-теги через localName
README widgets/report-forms: статус форм без stub-пометок, описание
composable + shared scss, правила работы (от бланка к XML).
Закрывает UX-запрос: бухгалтер видит формы «как документ», XML только на
скачивание, без raw-XML в UI.
Было: `import from 'extensions/reports/...'` падал в vite с «Failed to
resolve import», хотя tsc прошёл — paths/alias не совпадали между
tsconfig и vite. Сборка ловила ошибку, но долго.
Стало: `build.alias = { extensions: ./extensions }` в quasar.config.cjs.
Quasar CLI сам пробрасывает его и в `viteConf.resolve.alias`, и в
автогенерированный `.quasar/tsconfig.json` paths. Теперь `tsc`, `vue-tsc`,
IDE и Vite видят одно и то же — ошибка импорта ловится сразу в
typecheck, без полной сборки.
- РСВ: конвертирован 20-страничный TIF в PDF (bilevel CCITT G4, ~700 KB),
удалён дубликат 1151111_5.08000_11_1.tif, добавлен в PDF_BLANK_MAP
- FormStub.vue — базовая форма-заглушка: шапка со штрихкодом, ячейки ИНН,
ОКВЭД/ОКПО/ОКТМО, блок «в разработке», раскрываемый исходный XML
- Заготовки форм для всех отчётов: DusnForm, Ndfl6Form, RsvForm, PsvForm,
UusnForm (та же форма для UV_VZNOSY), Efs1Form — все делегируют FormStub
с правильными title/КНД/ВерсФорм. Заполнять по мере готовности
- ReportPreviewDialog: статичная мапа reportType→компонент, fallback
textarea заменён на «нет данных»
- README в widgets/report-forms/ — статус форм, архитектура,
инструкция «как перевести stub → полноценную форму»
- README reports-standarts: документирована конвертация TIF→PDF для РСВ
(snippet Python с пояснением почему bilevel, не RGB) + правило sync
PDF_BLANK_MAP ↔ PDF_AVAILABLE
- Backend: ReportStandardsService отдаёт XSD (cp1251→utf-8) и PDF-бланк
из components/reports-standarts/ с маппингом ReportType→папка→файл
- Резолверы downloadReportXsd и downloadReportBlankPdf (Query, chairman-only)
- DTO ReportXsdFile{content,fileName}, ReportBlankFile{content,fileName,mimeType}
- SDK: новые queries + selectors под obe формы, регенерирован zeus
- Frontend: методы useReportStore.downloadXsd/downloadBlankPdf c триггером
download в браузере (blob→a.click)
- Reference-форма BuhotchForm.vue: 2 листа (титул КНД 0710096 + баланс
ОКУД 0710001) с вёрсткой «как документ» — Times New Roman, A4, рамки,
штрихкод-заглушка, ячейки ИНН. Парсит XML через DOMParser, шапку
fallback-ом из requisites
- ReportPreviewDialog.vue заменяет ReportResultDialog: полноэкранный
диалог, слева форма-бланк, справа панель Скачать XML/XSD/PDF.
Для не-BUHOTCH форм пока fallback textarea с исходным XML
- Удалён старый ReportResultDialog.vue
Закрывает issue 989-8.
RequisiteField теперь принимает опциональные props mask/fillMask и прокидывает
их в q-input. SettingsPage:
- Рег. номер СФР: mask='###-###-######' → вводится как XXX-XXX-XXXXXX
- СНИЛС подписанта: mask='###-###-### ##' → XXX-XXX-XXX YY
Формат совпадает с @Matches в DTO и XSD ФНС/СФР — бухгалтер набирает «как
видит», backend принимает без ручной нормализации.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Было: UpdateReportRequisitesInputDTO.okpo и OrganizationDataInputDTO.okpo —
@IsString без паттерна/длины → сохранялись любые значения (11-13 цифр),
при генерации отчёта XSD падал «[facet 'pattern'] not accepted by [0-9]{10}».
Стало: @Matches(/^\d{8}(\d{2})?$/) — принимаем 8 (ГОСТ-юрлицо) или 10 (ФНС).
Сообщение «ОКПО — 8 или 10 цифр». SettingsPage: placeholder «10 цифр».
Главный контракт для поля — валидация XSD отчётности (согласование с ФНС),
UI-сохранение подгоняется под неё.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Использую enum из SDK Zeus — он генерируется из schema.gql при каждой пересборке,
всегда синхронен с backend. Захардкоженные 'database'/'manual' были time-bomb
(при переименовании enum-values на бэке UI молча переставал работать).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Проблема: бухгалтер на странице «Реквизиты отчётности» видел badge «Не заполнено»
у каждого поля, хотя данные заполнены и отображаются. При генерации любого отчёта
падал BadRequest с class-validator сообщениями «inn should not be empty, inn must
be a string, КПП должен быть 9 цифр, ...».
Причины:
1. UI: Nest сериализует enum RequisiteSource ключами GraphQL (DATABASE/MANUAL/EMPTY),
а getSource() в SettingsPage сравнивал с TS-значениями (database/manual/empty) —
всегда попадал в 'empty' → badge «Не заполнено». Фикс: toLowerCase-нормализация.
2. Backend: OrganizationDataInputDTO — опциональный override при generateReport,
но поля inn/kpp/orgName/ogrn/okved/oktmo/signerLastName/signerFirstName были
помечены @IsNotEmpty. Когда клиент не передавал organization, NestJS pipe всё
равно валидировал DTO и ругался на пустые required-поля. Фикс: все поля → IsOptional
+ nullable=true; паттерн-валидация применяется только если поле передано явно.
3. schema.gql / SDK регенерированы под nullable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- wallet-registry: id=0 → «Выпуск» (убрал отсебятину «(внешний источник)»)
- AccountsPage основная таблица: «Активный» blue-grey-8, «Пассивный» brown-7 + medium weight — не кричаще, но читаемо
- AccountsPage child-table: Дебет blue-grey-8, Кредит brown-7 — та же цветовая пара для консистентности
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Функция стала мёртвой после перехода Проводок по счетам на AccountIdCell
(accountCode вычисляется прямо в accountRows через /1000). Eslint
no-unused-vars ругался — удаляем.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared
- wallet-registry: +{id:0, 'Выпуск (внешний источник)'} — frontend-only (в wallets.hpp нет), для walletop issue/consume где wallet_from=0 или wallet_to=0
- account-registry: TS-копия LEDGER2_ACCOUNT_MAP из accounts.hpp (04/08/51/58/80/86) + getAccountName/getAccountMeta/storedIdToCode
- AccountIdCell: EntityIdBadge + tooltip с названием счёта, принимает code (51), не stored id×1000
OperationsPage
- «Проводки по счетам» используют AccountIdCell: debitCode/creditCode через /1000
AccountsPage
- Основная таблица: id счёта через AccountIdCell (tooltip с названием)
- Убрана цветная q-badge «Активный/Пассивный» → text-caption.text-grey-7
- Child-таблица: «Дебет/Кредит» не позитивным/негативным, а серым (text-grey-8) — убрали разноцветие
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- q-card flat bordered обрамляет таблицы «Движения»/«Проводки» в expand — тема подхватывается Quasar автоматически
- WalletIdCell: tooltip вынесен наружу EntityIdBadge (у badge нет default slot, tooltip внутри выпадал) — теперь подсказка с названием кошелька появляется при наведении
- AccountsPage: кнопка «Все проводки» → «Все операции»
- AccountsPage expand: колонка «открыть» — икона «fa-up-right-from-square» на каждой строке debit/credit, ведёт на OperationsPage?process_hash=…; убран цветной q-badge по типу — просто text-positive/negative
- WalletsPage expand: такая же икона «Показать операцию» на каждой строке движения
- OperationsPage: поддержан route.query.process_hash — фильтр применяется в loadHistory, chip «Операция xxxxxxxx» активного фильтра (removable → чистит URL)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared
- src/shared/lib/ledger2/wallet-registry.ts: 17 пар id→name (sync с contracts/cpp/lib/core/ledger2/wallets.hpp) + getWalletName()
- extensions/reports/shared/ui: WalletIdCell (EntityIdBadge + tooltip с названием из реестра, clip по клику), DirectionCell (стрелка + «Входящий/Исходящий/Перевод» одним текстом)
OperationsPage
- Колонка № = EntityIdBadge[:8] + tooltip; клик копирует полный processHash
- Chip операции flat: pastel bg + deep text по префиксу action_code, без иконки/outline
- Шапка expand переделана: цветная вертикальная полоска (accent по префиксу), крупное название, action_code моно, ID процесса через EntityIdBadge полный, примечание с иконкой note-sticky
- Таблицы «Движения» и «Проводки» в .row.q-col-gutter-md → col-12.col-md-6 (стек на мобилках)
- «Проводки по счетам»: убраны q-badge зелёный/красный — просто моноширинный текст
- Движения используют DirectionCell + WalletIdCell
WalletsPage expand — тот же DirectionCell + WalletIdCell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- typeorm-ledger2-state: global_sequence хранится как varchar(32), COALESCE с bigint падал «types text and bigint cannot be matched» — явный ::bigint каст на обеих сторонах сравнения и в MIN()
- WalletsPage expand: child-таблица движений теперь с колонками Из/В, стрелка ↓ входящее / ↑ исходящее / ↔ перевод; собственный кошелёк (props.row.id) выделен жирным
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend (controller):
- Ledger2Operation +walletFrom/walletTo (из data->>'wallet_from|wallet_to' для walletop)
- GetLedger2HistoryInput +parentApplyGlobalSequence: при раскрытии одного apply
фильтруем siblings диапазоном global_sequence до следующего apply того же
processHash — multi-effect процессы (cap.act2res) больше не сваливают трио
соседних apply в одну кучу
- SDK перегенерирован
Frontend (desktop/reports):
- OperationsPage: колонка № = processHash[:8] как q-badge в моноширинной стилистике
- Строка операции: q-chip цвета по префиксу action_code (reg/wall/cap/mkt/sov/mig)
- Expand разделён на 2 таблицы:
* «Движения по кошелькам» (walletop) со стрелками: ↓ зелёное входящее,
↑ красное исходящее, ↔ перевод; колонки «Из» / «В» / сумма
* «Проводки по счетам» (debit+credit) парой Дт→Кт в одной строке; id/1000
для счетов (51000 → 51)
- Суммы форматируются formatAsset2Digits (2 знака вместо 4): 100.0000 → 100,00
- Загрузка siblings через parentApplyGlobalSequence — пары Дт/Кт не разваливаются
- AccountsPage / WalletsPage: формат суммы 2 знака в child-таблицах
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- entities/Ledger2: добавлен Pinia store (loadAccounts/loadWallets/loadHistory + getAccountById/getWalletById), api/ теперь внутренний (move api.ts → api/index.ts), index.ts экспортирует только model + types
- entities/Report: убран export api из index.ts, store расширен loadRequisites/updateRequisites/checkReadiness
- entities/Account: добавлен прямой реэкспорт model (к легаси namespace ExtensionMode)
- 4 reports-страницы переведены на useLedger2Store()/useReportStore()/useAccountStore() — никакого прямого *Api в страницах
Эталон — entities/Meet. Смысл — публичный контракт entity = только model, чтобы позже её можно было упаковать в виджет/расширение.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- OperationsPage: нативные q-input type=date → q-popup-proxy + q-date (тема + иконка корректны на dark)
- ledger2 getHistory: фильтр accountId для apply-заголовков через подзапрос по process_hash — переход «К операциям»/«Все проводки» теперь находит связанные apply; siblings (walletop/debit/credit) остаются на прямом совпадении
- AccountsPage: убран hero-title «Счета» (как для OperationsPage)
- регенерирован SDK под новое поле processHash в schema.gql
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- убраны hero-subtitle на всех страницах (Операции, Кошельки, Счета, Отчётность, Реквизиты)
- OperationsPage: только apply-записи, реестр ACTION_CODE → русское название, FIO-обогащение, дочерние проводки в expand
- WalletsPage/AccountsPage: inline-expand с движениями/проводками без перехода на другую страницу
- SettingsPage: убрана секция «Готовность форм», кнопка сохранения наверх, фикс бейджа (source database→blockchain)
- DocumentsPage: русские названия форм (BUHOTCH→Бухотчётность и т.д.)
- backend: фикс quantity (amount вместо quantity), wallet_from/wallet_to в фильтре accountId, новый processHash-фильтр
- SDK: processHash добавлен в GetLedger2HistoryInput
- Report/api.ts: organization не отправляется как undefined (фикс class-validator ошибок)
Два пробела после [116] «reports встроенный extension»:
- reports отсутствовал в getDefaultApps(), поэтому не оседал в postgres через installDefaultApps на старте controller'а — приходилось ставить руками.
- ReportsExtensionModule не имел метода initialize(), и lifecycle-service кидал «moduleInstance.initialize is not a function» при runApp.
Фиксы:
- getDefaultApps(): +reports (enabled=true, builtinDefaultConfig).
- ReportsExtensionModule: async initialize() {} — stub по образцу BuiltinPluginModule (нет своего крона/состояния).
- postgres-init.ts: дублирующий seed в initExtensionsInPostgres (ON CONFLICT DO NOTHING) — чтобы boot:extra сразу давал столу появиться, не ждать следующего onModuleInit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zeus-скаляр ID разворачивается как непрозрачный `{}`, поэтому `:id='payment.id'`
не присваивался string-пропу кнопок. Явный `String(payment.id)` убирает обе ошибки
vue-tsc без изменения runtime-поведения (v-if уже гарантирует truthy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `appstore/extension/extension-app.module.ts` — `./interactors/*` → `../interactors/*` (файлы лежат в `application/appstore/interactors/`, модуль в соседнем `extension/` подкаталоге).
- `gateway/adapters/gateway-interactor.adapter.ts` — адаптер реализует `GatewayInteractorPort`, но не прокидывал два метода. Добавлены делегаты `executeIncomePayment(id, status)` и `expireOutdatedPayments()` в `GatewayInteractor` (сами методы уже реализованы в интеракторе — adapter просто их вызывает).
- `app.ts` — явная аннотация `const app: Express = express()` — иначе inferred-тип ссылается на внутренний путь `@types/express-serve-static-core` из pnpm-store (TS2742 portability warning).
`tsc --noEmit` теперь зелёный. Runtime поведение не меняется.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
По ревью 2026-04-20 (#3112334605): коммит РИД — самостоятельная фаза жизненного цикла
(Dr 08 / Cr 80 на каждом одобрении мастера), acceptance в signact2 только закрывает
накопленное 08 на 04. Matrix 2026-04-19 (Ангелина): «собирать на 08 частями по мере
коммитов, переносить на 04 когда РИД собран».
Изменения:
- `capital::approvecmmt` — после `upsert_creator_segment` считает
Δ `segment.available_for_program` (intellectual_cost − debt_amount) и эмитит
`COMMIT_RID` на эту дельту с `process_hash = project_hash`. Инвариант
`delta >= 0` защищён `eosio::check`.
- `capital::signact2` — `COMMIT_RID` больше не вызывается здесь; остаётся
`ACCEPT_RID` на полный `segment.available_for_program` + опциональный
`REPAY_LOAN`. Σ COMMIT_RID по сегменту == ACCEPT_RID → 08 закрывается в ноль.
- `process-hash-locator.ts` — новый process_type `cap.apprvcmmt` → table `projects`,
field `project_hash`. `cap.commit` action_code перемаплен с `cap.act2res`
на `cap.apprvcmmt`. `cap.act2res` теперь содержит только `cap.accept` + `cap.lnrepay`.
- `LEDGER2_CHART_OF_ACCOUNTS.md` + `_blago/17/req#50` — раздел «Процессы»
обновлён: добавлен `cap.apprvcmmt`, переформулированы комментарии по
`cap.commit` / `cap.accept`, зафиксирован инвариант.
Сборка capital.wasm / ledger2.wasm — зелёная.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Первый прогон показал: Σ wallets после миграции = 1_000_182_700 (testnet
накопил wallet-балансы при boot процессе создания 5 пайщиков), но AC8
ожидал ровно seedCash=5000. Это моя ошибка в ассерте — seed добавляет
seedCash к baseline, а не формирует всю сумму.
Фикс:
- Добавлена переменная baselineWalletsTotal (измеряется в seed-фазе ДО
первой миграции, как и baselineCashAcc/ShareAcc/EntryAcc).
- AC8 теперь проверяет totalWallets ≈ baselineWalletsTotal + seedCash.
Прогон полного pnpm test:all после фикса: 76 passed | 3 failed | 1 skipped.
Все 3 оставшихся fail — pre-existing, не связаны с ledger2-рефактором:
- registrator тесты ждут legacy `circulating_account`, отключённый ещё в
Epic 1 intro (b67d41b00f).
- capital-import тест на `status === 'active'` — логика importcontr.cpp
не менялась в этом PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Первый прогон тестов обнаружил архитектурную ошибку моей предыдущей
реализации миграции: я предположил, что `progwallet.blocked` в soviet —
это часть `legacy::ledger::accounts[80]` и поэтому при переносе вычитал
progwallet-суммы из share_money. Тестнет показал обратное:
Error: share_remain < 0 на voskhod
(share_money=3500 RUB, blagorost_invest=1 млрд RUB)
`legacy::ledger::accounts` и `soviet::progwallets` — параллельные системы
учёта, `progwallets.blocked` НЕ проводится через 80-й счёт. Любая бух-
проводка при переносе progwallet давала бы двойной учёт на 80.
Фикс:
- Удалены из actions.hpp записи `mig.blago` (TRANSIT_BLAGOROST) и
`mig.commit` (TRANSIT_COMMITMENT) — они были ошибкой. Осталось 18 ops.
- `migrate.cpp` разделён на два независимых потока:
A. Бухгалтерский — 4 inline apply(TRANSIT_*) по legacy::accounts
(min_share + share + entry + rid). Формулы упрощены:
share_money = cash_legacy − entry_legacy, rid_share = share_legacy − share_money.
B. Программные кошельки — новый хелпер `emplace_wallet_only()`,
ПРЯМОЙ wallets2.emplace для blagorost (9001) и generator (10001)
БЕЗ ledger2::apply и БЕЗ Dr/Cr. progwallets не влияют на 80/86/04.
- Инварианты упрощены: share_remain < 0 проверка убрана (невозможен), но
остались cash_legacy >= entry_legacy и share_legacy >= share_money.
process-hash-locator.ts: убраны `mig.blago` и `mig.commit` из маппинга
action_code → process_type. `mig.transit` остался с 4 action_code.
Документация LEDGER2_CHART_OF_ACCOUNTS.md обновлена: раздел миграции
разделён на A (4 проводки через apply) и B (2 прямых emplace без проводок).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Файл converttoaxn.cpp
----------------------
Возвращаем код к состоянию из Epic 1 intro `b67d41b00fa`:
Ledger2::apply(_soviet, _provider, CONVERT_TO_AXN, amount, coopname, process_hash, memo);
Что и почему было сломано:
- В Epic 1 intro `b67d41b00fa` стоял корректный scope=`_provider` —
ledger2-зеркало легаси-кошелька из строки 21 (там
`Wallet::sub_available_funds(_soviet, _provider, coopname, ...)` тоже
на `_provider`-scope). Ручное тестирование владельца прошло успешно.
- В code-review коммит `42e9b536c83` (Decision #7) я сам, без
воспроизводимого бага, свопнул аргументы на `Ledger2::apply(_soviet,
coopname, ..., _provider, ...)`, рассудив, что имя параметра
`coopname` «должно» означать кооп. Сломал: ledger2::apply валидирует
`cooperatives2_index` → username пайщика (то, что реально передаётся
в `coopname`-field action'а, см. system.adapter.ts:42 и memo
«от пайщика с username=...») там отсутствует → action упал бы с
«Неизвестный coopname: <username>».
- Cursor Bugbot на PR #357 справедливо указал на это. Признаю:
это я привнёс в предыдущем коммите, теперь откатываю.
Scope двойной записи ledger2 — тот же `_provider`, что и у легаси-кошелька;
инжекция AXN (строка 49) идёт на `coopname`-параметр — это не меняется.
Файл V2.1.0__process_registry_jsonb_indexes.ts
-----------------------------------------------
Cursor LOW: migration строил DDL через template-literal интерполяцию.
Сейчас значения — литералы рядом в коде, инъекции нет, но future-edit
с кавычкой в названии поля сломает SQL. Добавил whitelist `^[a-z0-9_]+$`
с throw при нарушении — чисто defensive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После [562-13] da8c443625 допуск (apprvappndx) и регистрация доли (regshare) разведены на два отдельных действия: signAppendix добавляет project_hash в appendixes контрибьютора, но сегмент в проекте больше не создаёт автоматически. Тесты 3-х спеков отражали старый «автоматический» флоу и падали на `Сегмент пайщика не найден` и `capital_contributor_shares = 0`.
Починка — без ослабления ассертов:
- processRegShare.ts: хелпер над capital::regshare
- A) "вклады… зарегистрированы автоматически" → "регистрируем доли через capital::regshare отдельным действием": спек сам вызывает regshare с user_shares = balance, жёстко проверяет segment.is_contributor=1, capital_contributor_shares==balance, Σ сегментов == Σ балансов, total_capital_contributors_shares==то же
- B) новый спек "regshare идемпотентен" — покрывает upsert-семантику regshare.cpp
- C) "тест ВЫСОКОЙ ТОЧНОСТИ": после signAppendix добавлен regshare по балансу — сегменты существуют до rfrshsegment/commitToResult; ассерт totalSharePercent≈100% остался
Результат: 60 passed | 1 skipped (61) против 3 failed | 56 passed | 1 skipped (60) до правки. Никаких .skip / try-catch-swallow — тесты честно зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Silent-catch в blockchain-consumer ACK'ил сообщения даже при упавшем saveDelta —
данные терялись безвозвратно; wallet::deposits/candidates2 не доходили до PG.
Consumer использовал `consumer-${random}` — каждый рестарт плодил zombie
со своими pending. Parser XTRIM MAXLEN=1000 при burst'ах удалял свежие
сообщения до того, как controller их прочитывал. rows$.subscribe без
error-handler'а молча убивал весь поток парсера при WS-разрыве.
Controller (blockchain-consumer.service.ts + redis-stream.service.ts):
- processDelta/processAction: убран try/catch-log-only → ошибки бросаются
до handleMessage, сообщение остаётся pending в группе для retry.
- processAction: убран setTimeout(3s) fire-and-forget → ACK теперь после
фактического save, а не до него.
- consumerName: стабильное 'coopback-main' вместо random.
- onModuleInit: recoverOwnPending (XREADGROUP STREAMS 0) доигрывает свои
pending после рестарта; reclaimStalePending (XAUTOCLAIM idle>5min)
забирает pending у зомби-consumer'ов.
- Периодические: XAUTOCLAIM 1/min + XTRIM MINID <first-pending-id> 1/30s
— Redis-память ограничена фактической consumption, не придуманным числом.
Parser (RedisNotifier + DeltaParser + ActionParser + config):
- publishEvent/publishDelta/publishFork: XTRIM убран (trim — роль consumer'а).
- REDIS_STREAM_LIMIT удалён из config.ts / .env-example / AGENTS.md.
- rows$.subscribe({next,error,complete}) вместо callback'а: ошибка/complete
триггерят process.exit, docker по restart:unless-stopped поднимает заново.
Integration tests (components/boot/src/tests/, +382 строк):
- process-registry.test.ts (Epic 4 e2e): 5 кейсов — reg.regist 2×apply,
wall.deposit, processes listing, hex-64 validation, 404 на unknown hash.
- ledger2-read-layer.test.ts (Story 1.23 e2e): 5 кейсов — on-chain↔
GraphQL срез accounts/wallets/history + фильтры actionCodes/accountId.
- shared/apiClient.ts: login chairman (eosjs-ecc) + gql + waitUntil.
Проверка на живой среде после fix'а:
- XLEN notifications = 70 (раньше 1001 фиксированно или потеря при burst).
- pending=0, lag=0 — ни одного silent drop, XAUTOCLAIM очистил зомби.
- blockchain_deltas впервые содержит wallet::deposits=3 (было 0).
- 18/18 boot integration + 77/77 controller unit тестов зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Все блокирующие находки code review.
CRITICAL
tests/unit/process-registry: переписаны под текущую архитектуру.
Epic 1 addendum удалил wjournal/journal, Phase A стал читать
blockchain_actions, но тесты остались мокать deltaRepo с _ledger2
якорями → 4 из 6 кейсов падали с NotFoundException. Commit message
Epic 4 "67/67 passed" стал неверен. Теперь:
- Phase A мокается через actionRepository (name='apply', action_code
в data, process_hash в data);
- Phase B — deltaRepo с правильными таблицами (candidates2, results,
deposits, etc.);
- processType выводится из ACTION_CODE_TO_PROCESS_TYPE (не из
delta.value.process_type);
- добавлены кейсы reg.regist (2 apply под одним hash) и mig.opening;
- account: '_ledger2' → 'ledger2' (bug с переименованием eeb4d50).
Результат: 8/8 ProcessRegistry-тестов зелёные, всего suite 77/77.
HIGH — PROCESS_HASH_LOCATOR: неверные имена таблиц/полей.
Проверено по C++ контрактам:
registrator::regs → candidates2 (field: registration_hash)
capital::segments удалена из локатора (project_hash, не result_hash)
capital::debts/result_hash→ debts (только для cap.debt, поле debt_hash)
capital::properties → pgproperties (field: property_hash)
marketplace::requests/request_hash → hash (поле именно hash)
Без этого getProcess для reg.regist/mkt.offereq/cap.act2*/cap.act2prp
возвращал пустое delta_history — UI был пустым на главном use-case.
HIGH — cap.act2res унификация (пользовательское уточнение).
Акт-2 это ОДИН процесс с ДВУМЯ эффектами (приём РИД в пай + погашение
займа), а не два процесса. В C++ ACTION_REGISTRY оба action_code
(cap.act2shr + cap.act2ln) уже указывают на CAPITAL_ACT2_RESULT =
"cap.act2res". Backend теперь то же:
- ACTION_CODE_TO_PROCESS_TYPE: cap.act2shr / cap.act2ln → cap.act2res;
- PROCESS_HASH_LOCATOR: cap.act2res → [results] (segments/debts
связаны через project_hash, не result_hash — не часть этого процесса);
- IProcessType: заменили cap.act2shr | cap.act2ln на cap.act2res.
UI при рендере process.actions видит два apply с разными action_code
→ группирует и показывает два эффекта раздельно внутри одной карточки
процесса (discriminator = action.data.action_code).
HIGH — Migration V2.1.0: индексы были без LOWER() → планировщик не
выбирал их, все getProcess упирались в seq-scan blockchain_deltas.
Теперь все expression-индексы построены на LOWER(value->>'field')
(совпадает с сервисным WHERE). Обновлены имена таблиц/полей согласно
новому PROCESS_HASH_LOCATOR. coopname не в индексе — OR (scope/value)
не покрывается одним expression-index, постфильтр дёшев (≤5 строк
per hash). CONCURRENTLY не используется (DEV-стенд).
HIGH — listProcesses GROUP BY двоил мульти-операционные процессы.
Было: GROUP BY action_code, hash, coopname — для reg.regist (entrfee+
minshare) и cap.act2res (shr+ln) показывало по две строки, totalCount
(COUNT DISTINCT hash) не совпадал с items.length.
Стало: GROUP BY LOWER(hash), coopname; MIN(action_code) для вывода
processType (у мульти-action процессов оба action_code маппятся в
один type, так что MIN даёт корректный тип).
HIGH — Убрать N+1 per-row counts в listProcesses.
Было: на каждую строку page 3 SQL (countActionsByHash +
countDeltasByHash + countDocumentsByHash). 100 строк × 3 SQL на
request → connection pool exhaustion под нагрузкой. Поля
actionCount/deltaCount/documentCount удалены из ProcessSummary DTO
и IProcessSummary interface. UI запрашивает getProcess(hash) при
раскрытии конкретного процесса — там счётчики выводимы из
actions.length/delta_history.length/documents.length.
Silent fixes:
- LIMIT/OFFSET в listProcesses: параметризация через $n вместо
литералов (безопасность + корректность при edge NaN).
- compareByBlock: tiebreaker по global_sequence (BigInt) когда
block_num + created_at совпадают — детерминированный порядок.
- blockchain-consumer: строгая проверка непустой строки в
value.coopname перед fallback на scope (пустой "" теряет дельту).
- Удалён dead LEDGER2_ACTION_NAMES.
- Устаревший комментарий ProcessRegistryDomainModule → blockchain_actions.
- add-test-user.ts: cleos-подсказки обновлены с wjournal на
candidates2/accounts2/wallets2.
Преднамеренно НЕ трогаю:
- Cross-tenant coopname validation (E1): оставлено для federation.
- Fork purgeAfterBlock (E6): вне scope Story 1.23, общесистемный
вопрос для отдельной задачи.
- Redis cache hardening (E9-E11): TTL 60s acceptable для MVP.
Пропущенный между Epic 1 (миграция плана счетов ledger2) и Epic 2 (генераторы,
ожидающие ×1000 offset id) backend-слой. До этого коммита:
- getLedger читал legacy laccounts (id 50/51/80/86);
- BuhotchGenerator фильтровал по ledger2-id (51000/80000/86000) → фильтр
пуст → реальный БУХ-баланс в ФНС уходил с нулями;
- WalletsPage показывал chartOfAccounts вместо общекооперативных
кошельков (1001/2001/3001/4001);
- OperationsPage фильтровал клиентски, пропуская записи на
невиданных ещё страницах пагинации.
### Backend (controller)
Domain `src/domain/ledger2/`:
- `interfaces/ledger2-account.interface.ts` — id (×1000), balance, debit/credit,
account_type (0=active / 1=passive).
- `interfaces/ledger2-wallet.interface.ts` — id, name, available, blocked.
Кошельки пайщиков живут в контракте soviet — сюда не попадают.
- `interfaces/ledger2-history.interface.ts` — Operation DTO + фильтр с
action/accountId/date-range/username.
- `ports/ledger2-state.port.ts` — единая точка чтения ledger2.
Application `src/application/ledger2/`:
- DTO (Ledger2Account/Wallet/Operation/HistoryResponse).
- `resolvers/ledger2.resolver.ts` — 3 query (Accounts/Wallets/History) с
@AuthRoles(chairman, member).
- `services/ledger2.service.ts` — маппинг domain → DTO, quantity/globalSequence
в String (BigInt safety).
- Ledger2Module — registered в AppModule.
Infrastructure:
- `typeorm-ledger2-state.repository.ts`:
- Accounts/Wallets: `SELECT DISTINCT ON (primary_key) value ... ORDER BY
primary_key, block_num DESC` из blockchain_deltas WHERE code='ledger2'.
Отдельной подписки не нужно — BlockchainConsumerService уже пишет deltas.
- History: `blockchain_actions WHERE account='ledger2'` + серверные фильтры
через параметризованный SQL (account_id → id/account_id/wallet_id JSON
поля; action_code и username через `data ->>`).
- Сортировка DESC по block_num+global_sequence.
report.resolver.loadLedger → ledger2Service.getAccounts. Теперь id совпадает
с BuhotchGenerator.ACCOUNT_GROUPS (×1000 offset), BUHOTCH даст реальные
балансы вместо нулей. ReportsExtensionModule теперь импортирует
Ledger2Module вместо LedgerModule.
### SDK
- Queries: `GetLedger2Accounts`, `GetLedger2Wallets`, `GetLedger2History`.
- Selectors с `MakeAllFieldsRequired`-валидацией.
- Queries.Ledger2 namespace, Selectors re-exports из `selectors/ledger2/`.
- schema.gql + zeus регенерированы (controller auto-gen при старте).
### UI (desktop)
- `src/entities/Ledger2/{api,types}.ts` — обёртки SDK.
- WalletsPage: источник → `ledger2Api.getWallets(coopname)`. Expand-row с
LedgerHistoryTable удалён (оставлена кнопка «Смотреть операции» — ведёт
в OperationsPage с прокинутым wallet_id). Отдельный Ledger2HistoryTable-
виджет не писал — избыточно для Story 1.23.
- AccountsPage: источник → `ledger2Api.getAccounts`. Добавлена колонка
«Тип» (Активный/Пассивный из account_type). Сальдо теперь реально берётся
из поля balance (не computed-difference), дебет/кредит — debitBalance/
creditBalance. Expand-история также удалён.
- OperationsPage: источник → `ledger2Api.getHistory`. Все фильтры ушли на
сервер (action-names, username, date-range, accountId). Клиентская
фильтрация удалена (она пропускала операции на ещё не догруженных
страницах). Server-side пагинация через @request, seq-guard против race'а
при быстрых кликах. route.query.account_id/wallet_id прокидываются в
filter.accountId.
### Проверено
- Controller TS: `npx tsc --noEmit` — 0 ошибок в ledger2/reports scope.
- SDK TS: 0 ошибок.
- Desktop vue-tsc: 0 ошибок в reports/entities/Ledger2 scope (PaymentCard.vue
имел 2 предыдущих ошибки, не связаны).
- Repo-layer SQL (через psql): возвращает корректные данные из
`voskhod.blockchain_deltas` (51000/80000/86000 accounts, 1001/2001/3001
wallets с актуальными balance/available).
- GraphQL endpoint: `POST /v1/graphql { getLedger2Accounts(coopname:"voskhod") }`
отвечает 401 Unauthorized (guards работают), без auth — ожидаемо.
### Scope не входит
- Миграция legacy getLedger/getLedgerHistory (кошельки пайщиков, member-UI).
Пока не трогаю — другие потребители ещё на legacy. Решим отдельно когда/
нужно ли списывать legacy ledger полностью.
DN1 (signerType persistence, настоящий баг отчётности):
Раньше председатель выбирал «Представитель» в SettingsPage → сохранял
signerRepDoc, но сам тип подписанта (chairman/representative) нигде не
персистился и при generateReport бэкенд брал `org?.signerType ?? 'chairman'`.
ФНС/СФР получали XML с ПрПодп=1 всегда, даже для представителя по
доверенности. Формально подпись не соответствовала указанной персоне.
Фикс:
- report_requisites.signer_type (varchar(16), nullable) — миграция
V2.0.1 идемпотентно добавляет колонку.
- UpdateReportRequisitesInputDTO.signerType + валидация @IsIn.
- ReportRequisitesViewDTO.signerType (String!, default 'chairman' из сервиса).
- MergedRequisites.signerType — чистый choice, не RequisiteField.
- report.resolver.resolveOrgFields берёт merged.signerType приоритетнее
org.signerType (input-override оставлен для бэк-совместимости).
- SDK: regenerated zeus + selector обновлён.
- SettingsPage: save шлёт signerType; load восстанавливает из ответа.
Silent UX-fix (DocumentsPage):
- B2: correctionsColumns → computed, иначе заголовки «На 31.12.YYYY»
замирают на init-годе и вводят председателя в заблуждение при смене.
- B5: debounce 400 + min/max 2000..2100 на year-input + UI-гвард перед
loadArchive, чтобы keystroke «2026 → 2» не спамил бэк 400-ответами.
- B6: onFilterChange сбрасывает page=1 перед loadArchive — иначе при
смене типа отчёта со страницы 5 попадаешь в пустой offset.
- B7/E1: triggerDownload — append anchor в DOM + setTimeout revokeObjectURL,
чтобы Firefox/Safari не обрывали скачивание "Network error".
- B9: openGenerate сбрасывает genPeriod=1 и genYear к дефолту, иначе
значение monthly (period=8) уносится в quarterly и бэк отбивает Max(4).
- B14: archiveTypeOptions строит label из reports (человекочитаемые
имена), а не тех-коды BUHOTCH/NDFL6.
- B15: corrections отправляется только для BUHOTCH — иначе бэк в
resolveCorrections лишний раз лезет в balance_corrections.
- E2: MIME для скачивания — application/xml без charset, чтобы работало
и для cp1251 ФНС и для utf-8 СФР (XML declaration сам сообщит парсеру).
- E3: guard `if (generating.value) return` — защита от двойного клика
до того как loading prop обновится.
- E5: lastArchiveRequestId-гвард — при быстрой смене страниц ответы
могут прийти не по порядку, рендерим только последний.
- E15: кнопка «Скачать XML» disabled если isValid=false + tooltip —
председатель в спешке больше не скачает невалидный отчёт в ФНС.
Silent UX-fix (прочее):
- E12: SettingsPage.save блокирует сохранение если signerType=representative
и signerRepDoc пустой — иначе XML пойдёт с пустым <СвПред НаимДок=""/>.
- B3/A4: AccountsPage «Сальдо» = available − blocked (а не копия available) —
пайщик больше не видит две одинаковые цифры в двух соседних колонках.
- B8/A11: OperationsPage scrollIntoView через data-seq-атрибут — ранее
document.querySelector('[key="op_N"]') всегда возвращал null, т.к.
Vue-шник :key не рендерится в DOM как HTML-атрибут.
- B10: OperationsPage не позволяет wallet_id молча затереть уже
выставленный account_id — явный if/else по query-params.
- B11: SettingsPage.loadReadiness — console.error внутри silent catch,
чтобы регрессии в селекторах не пропадали без следа.
- B12: reportRequisitesViewSelector теперь запрашивает signerType
(bundle вместе с DN1-regen).
- B13: RequisiteField — эмодзи 🔗/✏️/⚠️ заменены на q-chip :icon.
Преднамеренно не делаю:
DN2 (getReportPreview SDK+UI) — issue сам помечает follow-up.
DN3 (WalletsPage ≠ AccountsPage) — Story 1.23 (_ledger2 read layer).
DN4 (OperationsPage фильтр на backend) — зависит от Story 1.23.
BLOCKER: настоящая генерация BUHOTCH и отображение WalletsPage требуют
_ledger2 read layer в controller (Story 1.23). Сейчас getLedger читает
legacy ledger (id без ×1000), BuhotchGenerator ищет по ledger2-id
(×1000) — фильтр пуст, отчёт с нулями. Помечено отдельной задачей.
Закрыты DN1..DN9 code review:
DN1 (uusn): добавлена валидация period ∈ 1..4 с осмысленной ошибкой —
раньше silent fallback `?? '21'` маскировал invalid input и продуцировал
отчёт с НомерМесКварт вне {01..04}.
DN2 (memory): validateAgainstXsd теперь вызывает xmlDoc.free() в finally —
синхронно с проддлинновым XsdValidatorService; без этого long-running
jest --watch копил native libxml2 handles до OOM.
DN3 (signer guard): assertRepresentativeSigner теперь вызывается и в
NDFL6/RSV/DUSN, а не только в BUHOTCH — регрессионный щит на ПрПодп=2+
СвПред+НаимДок закрыт для всех ФНС-форм что реально используют
representative-подписанта.
DN4 (diagnostics): beforeAll пробрасывает xsdLoadError наверх — если
XSD-схема сломается, Jest покажет один чёткий fail вместо 16+
невнятных «XSD не загружена».
DN6 (DRY): EFS1_XML_NS_MAP экспортируется из XsdValidatorService;
тест импортирует вместо копипасты — single source of truth, не
разойдётся при обновлении СФР-namespace.
DN7 (UUSN coverage + README): добило UUSN тесты (well-formed, key attrs,
fileName, quarter codes, throw на invalid period) — Story 2.11
теперь реально покрыта для всех 8 генераторов. README фикстур получил
таблицу «файл → форма» с КНД/ВерсФорм.
DN8 (tautology):
- NDFL6: тест «27 нулевыми атрибутами» теперь реально считает 27 через
regex на открывающем теге вместо проверки 4 произвольных атрибутов.
- RSV: тест «<СвПред> НаимОрг» anchored к <СвПред ...> — раньше regex
матчил НаимОрг в НПЮЛ тоже и проходил даже когда СвПред лишилось атрибута.
- BUHOTCH: тест балансов теперь реально сверяет числа (150000₽ → 150 тыс)
+ добавлен отдельный тест на corrections → СумПрдщ/СумПрдшв.
DN9: UUID regex якорит точный {8}-{4}-{4}-{4}-{12} формат;
ReportRegistryService import через ES вместо require().
Преднамеренно не фикшу: B7/B11/B12 (low-nits follow-up).
- DN1: generateReport теперь отклоняет HIDDEN_IN_MVP типы (PSV/UV_VZNOSY/UUSN)
— раньше feature-flag работал только на UI (getAvailableReports +
getReportPreview), mutation была открыта напрямую в GraphQL.
- DN2: resolveSchemasDir() — robust резолвинг пути через env override
REPORTS_SCHEMAS_DIR + список кандидатов (src/, dist/../src/, cwd()).
Работает и под ts-node (текущий prod), и после возможной компиляции в dist/.
- DN3: withEfs1Chdir() — async-мьютекс через очередь Promise'ов поверх
process.chdir. Параллельные validate ЕФС-1 больше не могут перемешать cwd
(libxml2 резолвит xs:import относительно cwd, это глобальное состояние).
- P1: DUSN preview убран ctx.year-1 → ctx.year (единый контракт с генератором
после DN1 Chunk A).
- P2: validateByReportType с неизвестным XSD теперь isValid: false (раньше
был isValid: true с error в массиве — downstream сохранял is_valid=true
при наличии ошибки).
- P3: Preview «Сверка Актив=Пассив» с tolerance ±1 тыс. (ФНС 0710096 допускает)
— раньше строгое `===` показывало ложное «Нет» при валидных отчётах.
- P4+P5: toThousands / ledger2DisplayIdToLedgerIds экспортируются из
buhotch.generator; report-preview использует общий helper вместо дублированного
round1000 — единый источник правды для арифметики.
- P6: CP1251_ENCODING_RE — устойчивый регекс, покрывает double/single quotes,
case-insensitive, пробелы вокруг `=` (раньше не матчились варианты XSD).
- P7: libxmljs2 xmlDoc.free() в finally после validate — освобождение native
памяти; без этого каждый вызов validate утекает, long-running backend OOM.
- validate/validateByReportType теперь async — чтобы mutex и future I/O
работали корректно. Resolver await'ит результат.
Тесты: 61/61 report-generators зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- P1 generateReport: сохранение отчёта + upsertMany корректировок в одной
dataSource.transaction() (раньше два autocommit'а — при сбое корректировок
отчёт уже был записан, расхождение со snapshot).
- P2 BalanceCorrectionTypeormRepository.upsertMany: одним bulk-INSERT ON CONFLICT
вместо последовательного for-цикла (половинчатые записи при сбое невозможны).
- P3 ReportRequisitesTypeormRepository.upsert: настоящий .upsert() через
ON CONFLICT вместо read-modify-write (убрана гонка между параллельными
редактированиями одного coopname).
- P4 V2.0.0: CREATE EXTENSION IF NOT EXISTS "uuid-ossp" в начале up() —
DEFAULT uuid_generate_v4() иначе падает на свежей БД.
- P5 findLatest: period=undefined теперь НЕ означает «только annual»; возвращает
самый свежий отчёт за год независимо от периода (квартальные формы видят
«last generated» в дашборде). period=null — явно годовые. period=число —
точное совпадение.
- P7 loadLedger: убран try/catch-return-[] → ошибка ledger прокидывается,
нельзя «успешно» сгенерировать нулевой отчёт из-за недоступного ledger.
- P8 list: period=null → IS NULL, число → точно, undefined → не фильтруем
(симметрично findLatest).
- P9 generatedBy: убран fallback 'system'; при @AuthRoles(['chairman'])
currentUser не может быть пустым, FALLBACK маскировал баг декоратора.
- DN1 getAvailableReports: добавлен @AuthRoles(['chairman']) +
параллельный Promise.all вместо последовательного for-await.
- DN2 parseAmount: регекс `/(-?\d+(?:\.\d+)?)/` поддерживает отрицательные
суммы; раньше знак терялся и убыток становился прибылью.
- P6 DTO валидация: @IsNotEmpty, @Length/@MaxLength, @Matches (ИНН 10/12,
КПП 9, ОГРН 13/15, ОКТМО 8/11), @Min/@Max (year 2000..2100, period 1..54,
correctionNumber 0..999, limit 1..100, offset ≥ 0) — невалидные входы
теперь 400, а не SQL-ошибка 500.
- Throw NotFoundException/BadRequestException вместо plain Error (getReport,
getReportPreview HIDDEN_IN_MVP, generateReport readiness) — GraphQL отдаёт
структурированную ошибку.
Не трогали (по триажу):
- UNIQUE без report_type в balance_corrections — intentional (корректировки
общие для всех форм coop+year+account).
- organization_snapshot SNILS plaintext — отдельный compliance-тикет.
- Дублирующие индексы TypeORM — косметика.
Тесты: 61/61 report-generators зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Крит 3: buhotch/dusn/ndfl6/rsv/uusn/uv-vznosy — generateFileName() кэшируется в generate(),
передаётся в buildXml(input, idFile) одним параметром. Раньше было 2 вызова →
2 разных UUID, filename на диске и ИдФайл в XML не совпадали → отказ ФНС-приёмки.
- Крит 6 / DN1: dusn.generator убран year-1; единый контракт input.year =
«год за который отчитываемся» во всех ФНС-генераторах. UI по умолчанию
подставляет currentYear-1; тест ДУСН обновлён (было year:2026→ОтчетГод:2025,
стало year:2025→ОтчетГод:2025).
- Крит 9: ledger2DisplayIdToLedgerIds теперь игнорирует subaccount — в ledger2
субсчета 86.x удалены (Epic 1 addendum), все корректировки пользователя
с display-id «86.01»/«86.1» резолвятся в родительский счёт 86000.
Раньше «86.01» → 8601000, и корректировка молча не применялась.
- Крит 10: fss4.sfrDateTime — hardcode Moscow TZ (+03:00). Раньше брался
локальный TZ хоста → UTC-контейнер писал «+00:00» не соответствуя СФР.
- M2: uv-vznosy.generator — валидация input.period ∈ [1..12] с явной ошибкой
вместо silent «НомерМесКварт=undefined»/квартал-overflow.
- M3: buhotch BalanceRow — переименовал поля prdш/prdsh (Cyrillic/Latin-mix)
в prdPrev/prdPrePrev (Latin), чтобы избежать confusable-identifier багов.
- M5: buhotch correctionNumber валидация (целое 0..999) — раньше отрицательные/дробные
значения молча писались в атрибут, ФНС XSD отклонял.
Не трогали (по решению review): РСВ/6-НДФЛ/FSS4 структуру (пустой <РасчетСВ/>,
ставка "13", Cyrillic namespace) — они соответствуют эталонам ВОСХОДа, которые
уже были приняты налоговой. ДУСН ПризНП=1 (кооператив не платит зарплату).
Тесты: 61/61 report-generators зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- P1 AC5: падать если accounts[99] не создан при свежем миграционном прогоне (было silent-pass на undefined) [ledger2-migrate.test.ts]
- P2 AC6: expect(acc).toBeDefined() перед чтением .balance у cash/share/entry (TypeError → понятный fail) [ledger2-migrate.test.ts]
- P3 AC7: snapshot meta + балансов ДО и ПОСЛЕ повторного migrate() — проверяем что migrated_coops/last_migrated_coop_index/debit_balance(51)/credit_balance(80) не изменились [ledger2-migrate.test.ts]
- P5 Заменить literals Number().toBe(0)/(1) на LedgerAccountType.ACTIVE/PASSIVE enum (экспортирован из walletUtils) — устойчивость к переименованию типов [ledger2-migrate.test.ts + walletUtils.ts]
- P6 depositToWallet(amount): Number.isFinite + amount > 0 guard — NaN/Infinity/отрицательные значения теперь дают понятную ошибку до сериализации в asset [depositToWallet.ts]
- P7 Dead-code cleanup в wallet.test.ts: убраны tester1/walletProgramStates1/addUser — никогда не использовались в ассертах; вместо этого добавлены expect().toBeDefined() на wallet/program/depositId/userWallet
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
K1 migrate unique process_hash:
- migrate.cpp генерирует детерминированный hash = sha256("mig::"+coopname+"::"+action_code)
→ каждая opening-проводка различима в реестре, не сливается в zero-hash.
K2 cross-account Phase A scan:
- V2.1.0 миграция: idx_actions_ledger2_process_hash переименован в idx_actions_process_hash
и сделан полным (без WHERE account='ledger2') — обслуживает и ledger2-only,
и cross-account запросы.
- ProcessRegistryService.getProcess теперь собирает ВСЕ actions по (process_hash, coopname),
не только ledger2. В view.actions попадают source-action (wallet::depcpl,
registrator::regist, capital::*, marketplace::*, soviet::*) + inline-трио ledger2.
- countActionsByHash тоже cross-account — отражает полный счёт процесса.
K3 countDeltasByHash rewrite:
- Убран full-scan `LOWER(d.value::text) LIKE '%hash%'`.
- Замена: per-location точный запрос по PROCESS_HASH_LOCATOR (индексы idx_deltas_*).
- countDeltasByHash/countDocumentsByHash теперь принимают processType для локализации scope.
- listProcesses фильтрует строки с неизвестным action_code (вместо fallback'а на raw code).
K4 act2shr/act2ln separation:
- Убрана агрегация cap.act2shr/cap.act2ln → cap.act2res.
- Каждый action_code теперь собственный process_type: share-вклад и погашение
займа в акте-2 показываются РЯДОМ (один process_hash), но как разные бух-операции.
- PROCESS_HASH_LOCATOR получил оба ключа; IProcessType union обновлён
(cap.act2res → cap.act2shr | cap.act2ln).
K6 account id scale consistency:
- ChartOfAccountsEntity.INTANGIBLE_ASSETS=4 откат активации — эта entity читает
legacy `ledger` (scale 51, 80, 86 без ×1000), РИД живёт в ledger2 (id=4000).
Для ledger2 плана счетов нужна отдельная entity с offset ×1000 (TODO в коде).
- Reports-генераторы (buhotch, report-preview) уже используют ×1000 консистентно.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- D1: sender-guard в walletop/debit/credit — допустим только inline из apply (парность double-entry гарантирована, top-level вызов с ledger2@active запрещён)
- D2: TODO(payer) в walletop/debit/credit — payer=get_self() временно до общего перехода на coopname+eosio.code
- D3: read_legacy_balances аварит на неожиданном legacy id + проверяет символ валюты (silent-drop заменён на check(false))
- D4: повторный migrate() после полного прогона — тихий no-op без eosio::check (тест AC7 обновлён)
- apply: require_recipient после валидации coopname (не до)
- walletop: ISSUE требует wallet_from==0, BLOCK/UNBLOCK требуют wallet_to==0
- walletop/debit/credit: убран require_recipient (дублировал notification из apply)
- accounts2::is_empty() теперь включает поле balance
- actions.hpp: добавлен static_assert(wallets_exist_in_registry) — compile-time проверка что wallet_id из ACTION_REGISTRY существуют в LEDGER2_WALLET_REGISTRY
- migrate: guard против permanent-lock на пустой таблице cooperatives
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FIXES после live-прогона через pnpm run reboot + add-test-user:
1) parser/src/config.ts: добавлены ledger2+marketplace в subscribedContracts —
без этого parser не пускал ledger2-дельты в Redis stream и controller не
получал wjournal/journal, т.е. весь pipeline стоял.
2) controller/src/infrastructure/blockchain/blockchain-consumer.service.ts:
processDeltaDelayed фильтровал по `delta.value?.coopname`, но ledger2
(wjournal/journal/wallets/accounts) и большинство кооп-scope таблиц
хранят coopname В SCOPE, а не в value.jsonb. Добавлен fallback на scope:
`deltaCoop = delta.value?.coopname ?? delta.scope`.
3) domain/process-registry/services/process-registry.service.ts:
- LEDGER2_CODE изменён с `_ledger2` на `ledger2` (имя on-chain аккаунта
реальное, без подчёркивания — verified через cleos get abi ledger2).
- phase A + listProcesses: coopname-скоупинг по `d.scope` (вместо
несуществующего value->>'coopname' для ledger2 journals).
- phase B (scanEntityDeltas): принимает оба варианта — `scope = $coop
OR value->>'coopname' = $coop`, чтобы охватить и per-coop-scope
таблицы, и singleton-scope контракты (registrator.regs).
- LOWER() обе стороны при сравнении process_hash: ончейн хранит
checksum256 uppercase, а нормализация API — lowercase.
- listProcesses возвращает processHash в lowercase через LOWER() в
SELECT (единообразно с getProcess).
- countDeltasByHash/countDocumentsByHash: LOWER() + scope=coop.
4) migrations/V2.1.0: expression-индексы ledger2 journals теперь на
(process_hash, scope) и (process_type, scope) — совпадают с фактическим
where-условием сервиса.
5) cooptypes/common/names: `_ledger2.production/testnet = "ledger2"`
(без подчёркивания, соответствует on-chain имени).
6) boot: добавлен CLI `pnpm run cli add-test-user <username>` для smoke-
проверки Epic 4 на живом стенде после reboot. Также установлен
`registration_hash: generateRandomSHA256()` в:
- boot/init/infra.ts: adduser(ant) + adduser для 4 дополнительных
членов совета (boot:extra mode);
- boot/init/participant.ts: addUser+addUser2.
ПРОВЕРКА (live через curl + JWT подписанный JWT_SECRET из .env):
cleos get table ledger2 voskhod wjournal — 2 записи:
id=0 reg.minshare, process_type=reg.regist, process_hash=<sha256>
id=1 reg.entrfee, process_type=reg.regist, process_hash=<sha256>
(тот же hash — мульти-операционный процесс reg.regist)
query { process(hash:"28fe0b46...",coopname:"voskhod") }
→ process_type="reg.regist"
delta_history: 4 (2 wjournal + 2 journal)
actions: 3 (registrator::adduser + 2×ledger2::apply)
documents: []
query { processes(filter:{coopname:"voskhod"}, pagination:{...}) }
→ totalCount=1, processHash lowercase, actionCount=3, deltaCount=4
Redis cache: ключ `process::voskhod::<hash>`, TTL=60s ✓
Auth: без JWT → 401 Unauthorized ✓
pnpm test — 67/67 passed (все unit-тесты по-прежнему зелёные).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
КОНТРАКТЫ (C++/CDT):
- actions.hpp: ledger2_ops::* переименованы с контрактным префиксом (reg./wall./cap./mkt./sov.),
ActionRegistryEntry.process_type + static_assert unique(action_code); runtime-проверок нет —
CPU-бюджет блокчейна не тратится на то, что гарантировано сборкой.
- process_types.hpp: новый namespace с 9 константами процессов (мульти-операционные cap.act2res,
mkt.offereq, reg.regist явно разрешены).
- wjournal/journal: поля process_type/process_hash + secondary indexes byproctype/byprochash.
- apply(): document_hash → process_hash; emplace пишет process_type из registry в оба журнала.
- registrator::adduser: +checksum256 registration_hash параметр, inline sha256(username_str)
удалён — все entity-хэши теперь приходят извне и годятся как process_hash.
- soviet::converttoaxn: +checksum256 process_hash параметр; отдельной ончейн-таблицы конверсий
нет (одноактовый процесс), бэкенд передаёт statement.hash явно.
- Все 6 затронутых контрактов собираются (ledger2/registrator/soviet/wallet/marketplace/capital).
БЭКЕНД (NestJS):
- domain/process-registry/: ProcessRegistryService c двухфазным алгоритмом (anchor scan по
_ledger2/wjournal+journal → fan-out по PROCESS_HASH_LOCATOR → документы через DocumentAggregator);
Redis TTL 60s; fail-fast на неизвестный process_type; hard limit 200 с ошибкой (не обрезанием).
- listProcesses: DISTINCT ON (process_hash) по wjournal с пагинацией через PaginationInputDTO +
createPaginationResult<ProcessSummary>.
- application/process-registry/: Resolver с Query.process + Query.processes под
GqlJwtAuthGuard + AuthRoles(['chairman','member']).
- migrations/V2.1.0__process_registry_jsonb_indexes.ts: 11 partial-indexes на blockchain_deltas
(ledger2 journals + все таблицы из PROCESS_HASH_LOCATOR) — без них getProcess делает seq scan.
- install/participant interactors: прокидывают sha256(username) как registration_hash; system.adapter
прокидывает statement.hash как process_hash в converttoaxn.
- redis.module.ts: REDIS_PROVIDER в exports (нужен для ProcessRegistryService inject).
SDK:
- cooptypes: Ledger2Contract (IApply/IProcessType/IProcessView/IProcessSummary/IProcessesFilter/
IProcessAction/IProcessDelta/IProcessDocument); IAdduser.registration_hash,
IConverttoaxn.process_hash; _ledger2 в common/names.
- sdk/src/zeus: автогенерация controller schema.gql → ProcessView/ProcessSummary/ProcessesFilter
+ query.process/query.processes в namespaces Queries.
- Controller startup OK, SDK build OK (343kB).
ТЕСТЫ:
- tests/unit/process-registry/: 6 unit-кейсов ProcessRegistryService (одноактовый sov.axncnv,
мульти-операционный cap.act2res с 2 entity-локациями, wall.deposit без документов, валидация
hex-64 hash, fail-fast на unknown process_type, 404 без якорей).
- jest.config.js: moduleNameMapper для ~/ алиасов (иначе ts-jest не резолвит импорты).
- pnpm test — 67/67 passed (61 существующих + 6 новых).
ОСТАЛОСЬ (4.12):
- Smoke E2E на testnet — требует деплой контрактов, репарсинг с генезиса, ручной прогон
5 сценариев (регистрация/депозит/долг/акт2/конвертация) + сохранение фактических результатов.
Чек-лист готов в _bmad-output/implementation-artifacts/4-12-smoke-results.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Закрыть Epic 3 целиком (Stories 3.1–3.8), чтобы отчёты ФНС/ФСС были доступны как полноценный workspace вместо голой страницы генерации, с деплинком между операциями/кошельками/счетами и с валидацией реквизитов до нажатия «Сгенерировать».
- extensions.registry.ts: раскомментировать reports-модуль, чтобы бэк отдавал его в AppRegistry.
- desktop/extensions/reports/install.ts: 5 маршрутов (operations/wallets/accounts/documents/settings), роль chairman на всех.
- OperationsPage: virtual-scroll, клиентские фильтры (период/action/user/memo), expand с проводкой+движением+кнопками «К счёту»/«К кошельку», scrollIntoView по operation_id из query.
- WalletsPage: таблица без writeoff, expand с LedgerHistoryTable по account_id=wallet_id, кнопка перехода в OperationsPage.
- AccountsPage: моноширинный displayId (дробная нотация 86.01), expand c журналом проводок, кнопка «Журнал».
- DocumentsPage: архив getReportHistory + isValid-чипы + «Скачать» через getReport(id), feature-flag 5 форм (BUHOTCH/NDFL6/RSV/DUSN/FSS4), индикаторы readiness, форма корректировок прошлых периодов для BUHOTCH (передаётся как corrections[] в generateReport).
- SettingsPage + RequisiteField: 4 раздела (organization ro/классификаторы/СФР/подписант), бейдж источника (блокчейн/manual/empty), индикаторы готовности по 5 формам через checkReportReadiness, scrollIntoView к полю по query.focus.
- entities/Report: reportApi обёртка над SDK (getAvailableReports, getReport, getReportHistory, getReportRequisites, checkReportReadiness, generateReport, updateReportRequisites).
- SDK: selectors/queries/mutations reports/, namespaces Queries.Reports + Mutations.Reports. Регенерация Zeus-типов через generate-client в контейнере coopback (libxmljs2 пересобран под Node 22).
- schema.gql: обновлена штатным autoSchemaFile при старте controller в coopback.
- удалён старый ReportsPage/ — DocumentsPage единственная точка входа.
typecheck desktop и SDK — чистые; controller имеет 3 пре-existing ошибки в appstore/extension и gateway, не связанные с Epic 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добыта XSD EFS-1_2024-01-01.xsd с сайта СФР + 7 зависимостей, положена в
schemas/efs1/ с двумя патчами: кириллические URI в namespace на ASCII
(libxml2 отвергает http://пф.рф/) и сдвиг дат 2024-01-01 → 2026-01-01 под
формат КОНТУР-ЭКСТЕРН; XsdValidatorService определяет кодировку XSD по
заголовку и делает chdir для резолвинга xs:import; генератор заполняет
<ДатаЗаполнения> по xs:date. Добавлен XSD-тест, 61/61 зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- components/controller/tests/fixtures/reports-references/ — 5 эталонных XML (NO_BOUPR/NO_NDFL6.2/NO_RASCHSV/NO_USN/EFS1) с вымышленными реквизитами (ИНН 7701234567, ПК "Ромашка", Петров П.П.) + README
- тест переключён на новый REFERENCES_DIR; baseInput также теперь ромашка
- добавлен кейс «EFS1: та же структура ОСС» — 58/58 зелёных
- story 2.7 (ЕФС-1) переведена в done: XSD СФР нет в публичном доступе, структурная сверка с фикстурой закрывает регрессии
- .gitignore расширен шаблонами для реальных XML с ИНН 9728130611 / рег.СФР 1118018397, чтобы подобные образцы не утекали в гит из любых подпапок reports-standarts/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- новые 57 jest-кейсов в tests/unit/reports/ покрывают BUHOTCH, NDFL6, RSV, PSV, DUSN, ЕФС-1, UV_VZNOSY, UUSN
- структурная сверка с эталонами ВОСХОДа (reports-standarts/) + XSD-валидация по 6 доступным схемам через libxmljs2
- pnpm run test на controller переведён с echo-заглушки на jest (интеграция сохранена в test:integration)
- попутно пофикшены баги в uv-vznosy/uusn: атрибуты Период/НомерМесКварт не проходили XSD (был "21/01" вместо одного из {21,31,33,34}; номер без zero-padding)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Story 2.1: entity generated_reports + balance_corrections, TypeORM-репозитории, идемпотентная Flyway-миграция V2.0.0.
Entity авто-дискаверятся через глоб src/extensions/**/entities/*entity.{ts,js}. Без UNIQUE на
(coopname,report_type,period) — пересоздание допустимо; UNIQUE на (coopname,year,account_display_id)
для upsert корректировок.
- Story 2.2: XsdValidatorService поверх libxmljs2 с кэшированием всех 6 XSD из schemas/,
перекодировкой cp1251→utf8 и мержем ошибок в GeneratedReportDTO.errors. Смоук: эталонный BUHOTCH
парсится, .validate() корректно отдаёт как позитив, так и детальные семантические ошибки.
Находка для Story 2.3: установленная XSD — v5.09 КНД 0710099 (коммерческая), эталон ВОСХОДа — v5.04
КНД 0710096 (форма НКО). Либо добыть XSD 5.04, либо переключить генератор на 5.09.
— migrate.cpp: для каждой ненулевой laccount сумма total=available+blocked кладётся в ledger2::accounts[legacy_id*1000] в правильное плечо оборотов (ACTIVE/A-P → debit_balance, PASSIVE → credit_balance); при наличии целевого фонда параллельно заполняется wallets (SHARE_FUND=2, ENTRANCE_FEES=3, LONG_TERM_LOANS=6)
— маппинг legacy→ledger2 задокументирован в PRD §4.1.5 таблицей (FR-L-13) + issue 989-1: 51→51000(A,Dr), 80→80000(P,Cr)+wallet2, 861→861000(P,Cr)+wallet3, 67→67000(P,Cr)+wallet6
— зафиксирована follow-up миграция (Story 1.11 backlog): вычленение минимальных паевых взносов из wallets[SHARE_FUND=2] в wallets[MIN_SHARE_FUND=1] по количеству пайщиков × размер обязательного пая; accounts не трогается (проводка Dr51/Cr80 одна и та же)
— интеграционный тест расширен с 8 до 9 AC: AC5/AC6 теперь проверяют что BANK_ACCOUNT и MEMBER_DEBT мигрируют в accounts (Dr), но не создают кошельков; AC7 отдельно проверяет credit_balance для 80000/861000/67000
— legacy одноконтурный, поэтому Σ Dr ≠ Σ Cr после миграции — это ожидаемо и задокументировано в миграции
| `02-extension-config` | Запись `extensions.capital` в postgres со всеми `*_done=true` (dev-shortcut). Без неё controller отдаёт 500 «Конфигурация расширения capital не найдена», и UI редиректит на страницу адаптации | работающий postgres |
| `03-projects` | 12 проектов и 30 компонентов из `_blago/INDEX.md` | `01-programs` |
| `04-contributor` | Регистрация председателя `ant` как Contributor (программно через `Capital.GenerateCapitalRegistrationDocuments` + `CompleteCapitalRegistration`). Без неё UI после адаптации показывает заглушку «Ранним участникам» и не пускает в Мастерскую | `02-extension-config`, controller :2998 |
`phases/02b-real-onboarding.ts` — зарезервированная фаза для будущей задачи: реально провести 5 решений совета через SDK (vote ×3 → authorize → exec). Не подключена к диспетчеру; нужна только когда будем документировать сам процесс адаптации в UI.
expect(generatorDelta,`${WALLET_GENERATOR_FUND} ушёл в дефицит (Σ COMMIT_RID < Σ ACCEPT_RID для проекта ${componentProject.project_hash.slice(0,12)}…) — регрессия approvecmmt?`).toBeGreaterThanOrEqual(-0.0001)
if(generatorDelta>0.0001){
console.log(`⚠️ ${WALLET_GENERATOR_FUND} остаток ${generatorDelta.toFixed(4)} RUB — это нераспределённые доли (skipped-сегменты, роли без is_contributor); ожидаемо для этого сценария.`)
}
// Дополнительно: w.cap.blago (BLAGOROST_FUND) должен получить хоть какие-то
// средства (в проекте есть intellectual contribution, и хоть один сегмент
// дошёл до signact2). Точная сверка с total_contribution невозможна:
// signact2 параллельно вызывает REPAY (LOAN_ISSUED → SHARE_FUND_PAY)
// на debt_amount, который не идёт на w.cap.blago. Поэтому только sanity-чек,
// что путь ACCEPT_RID(w.cap.gen → w.cap.blago) вообще работает.
| `cap.accept` | TRANSFER | **04/08** | 10001 → BLAGOROST_RID 9002 | **Приём РИД в НМА** — эмитится на `capital::signact2` на полный накопленный `available_for_program`. Закрывает 08 в ноль. |
- У одного проекта может быть множество одобренных коммитов — все группируются в один процесс.
**Акт-2 Благорост** (process_type `cap.act2res`) — подписание акта-2 председателем в `capital::signact2`:
1.`cap.accept` (Dr 04 / Cr 08) — 08 закрывается, РИД принят в НМА на полный накопленный `segment.available_for_program`.
2.`cap.lnrepay` (Dr 80 / Cr 58) — опционально, если у пайщика был заём.
**Инвариант**: Σ `cap.commit` по сегменту (одобренные коммиты) == `cap.accept` по этому же сегменту в signact2 → `GENERATOR_COMMIT` (10001) закрывается в ноль, счёт 08 по сегменту закрывается в ноль.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.