sync-arch: миграция controller на parser2 ParserClient — форки, идемпотентность, retention [C28-11..16] #68
Open
claude
wants to merge 28 commits from
parser2 into dev
pull from: parser2
merge into: C9S:dev
C9S:main
C9S:dev
C9S:marketplace2
C9S:testnet
C9S:fix/598-51-membership-fee-and-type-cleanup
C9S:feat/capital-canon
C9S:feat/598-supplier-registry
C9S:feature/capital-cpp-private-data-templates
C9S:fix/soviet-install-validation
C9S:feat/ku-selforg
C9S:feat/membership-exit
C9S:feat/marketplace-admin-orders
C9S:fix/capital-share-onproject-event
C9S:ci/independent-env-deploy
C9S:fix/registration-pending-display
C9S:coopID
C9S:feat/expense-chassis-mvp
C9S:fix/zip-utf8-flag
C9S:feat/coopid-auth-ui
C9S:feat/coopid-7-signing-bridge
C9S:feat/coopid-12-recovery-account-from-confirm
C9S:feat/coopid-epic-9-metrics
C9S:feat/coopid-epic-6-access-rules-cleanup
C9S:feat/coopid-epic-12
C9S:feat/coopid-epic-11
C9S:feat/coopid-epic-7
C9S:feat/coopid-epic-9
C9S:feat/coopid-epic-6
C9S:feat/marketplace-process-registry
C9S:feat/marketplace-notifications-map
C9S:feat/marketplace-ku-economy
C9S:savanna
C9S:feat/apps-catalog-finish
C9S:feat/epic-13-billing-package-powerup
C9S:feat/coopid-epic-4
C9S:feat/coopid-epic-3
C9S:feat/blagorost-on-chassis
C9S:feat/coopid-epic-1
C9S:feat/marketplace-coop-stock
C9S:feat/marketplace-live-catalog
C9S:feat/coop-invest-blagorost
C9S:feat/ff-release-flow
C9S:fix/marketplace-offer-instance-not-template
C9S:fix/extension-config-lost-update
C9S:feature/C28-28-email-relay
C9S:feat/E16-onsite-sign-gate
C9S:feat/pwa-version-watch
C9S:fix/participants-registry-status-badges
C9S:feat/E16-coop-categories
C9S:fix/sanitize-blockchain-error-toast
C9S:fix/wallet-withdraw-no-phantom-payment
C9S:fix/agenda-program-offer-links
C9S:feat/notification-center-dc-v3
C9S:fix/chatcoop-livekit-room-name
C9S:feat/delete-inactive-participant
C9S:feat/registration-ledger2-suspense
C9S:feat/registration-refund
C9S:feat/registration-payment-dedup
C9S:perf/factory-parallel-fetch
C9S:feat/E16-sklad-rework
C9S:fix/decision-createproject-tsc
C9S:perf/factory-weasyprint-warm-pool
C9S:feat/E16-korzina-zakaz-agregat
C9S:fix/agenda-pending-new-question
C9S:feat/soviet-negative-consensus
C9S:feat/registration-payment-decline
C9S:feat/coop-standards-audit
C9S:feat/mkt-return-chairman-cosign
C9S:feat/registration-fsm
C9S:feat/pwa-reliable-update
C9S:parser2-block-time-and-version
C9S:fix/gorozhane-phantom-cleanup
C9S:fix/remove-bank-kpp
C9S:feat/E9-3b-sub-subscribe
C9S:feat/E9-9-approve-moderation
C9S:feat/E10-5-on-chain-watcher
C9S:feat/E10-4-install-pipeline
C9S:feat/E9-3b-ui-publish-package
C9S:feat/E1-tspp-docs-real-text
C9S:feat/E10-3b-dynamic-supergraph
C9S:apps-catalog
C9S:feat/E9-9-3-b-publish-mutation
C9S:feat/sig-v2-export
C9S:feat/E10-1-federation-subgraph
C9S:parser2-epic-4-forks
C9S:feat/decision-authorize-backend
C9S:feat/blagorost-program-expenses-ui
C9S:parser2-epic-6-canonical-storage
C9S:wip/pre-e16-desktop-docs
C9S:feat/E15-min-volume-per-ku
C9S:feat/blagorost-E2-E5-program-expenses
C9S:feat/epic-14-billing-notifications
C9S:blagorost
C9S:feat/E14-explicit-shipment
C9S:feat/marketplace-collective-supply
C9S:feat/blagorost-E1-contract
C9S:feat/C28-21-pg-signed-documents
C9S:provider-billing
C9S:fix/desktop-format-asset-errors
C9S:feat/marketplace-docs
C9S:feat/epic-12-billing-subscriptions
C9S:fix/min-soviet-members-3
C9S:parser2-epic-3-transport
C9S:feat/marketplace2-drop-blocked
C9S:worktree-design-wave1
C9S:feat/notif-mobile-canon
C9S:fix/vue-tsc-desktop
C9S:worktree-marketplace-docs-impl
C9S:feat/chatcoop-hide-matrix-room-id
C9S:feat/epic-8-coop-registry-provider
C9S:parser2-epic-2-idempotency
C9S:feat/chatcoop-secretary-rooms
C9S:feat/remove-l3-blocked
C9S:ci-trial-typecheck-2026-05-23
C9S:fix/controller-revert-transpileonly
C9S:chore/mongo-standalone-local
C9S:chore/ci-unify-release-workflows
C9S:feat/controller-swc-builder
C9S:feat/chatcoop-transcription-memo
C9S:fix/mono-base-ca-certificates
C9S:fix/wallet-withdraw-gateway-timing
C9S:feat/ledger2-burn-blocked-withdraw
C9S:fix/boot-tests-wallet-signagree-v2
C9S:chore/trustees-ownership
C9S:feat/capital-w-cap-gen-cooperative
C9S:chore/re-review-E5-apl-fail-fast
C9S:chore/re-review-E4-fail-fast-tx-hash
C9S:chore/re-review-E3-fix-withdraw-stub
C9S:chore/review-E3-vitrina-fixes
C9S:chore/review-E10-design-fixes
C9S:chore/review-E8-spisanie-fixes
C9S:chore/review-E7-vozvrat-fixes
C9S:chore/review-E6-vydacha-fixes
C9S:chore/review-E5-postavka-fixes
C9S:chore/review-E4-order-fixes
C9S:feat/epic0-onboarding-harness
C9S:chore/marketplace2-sync-dev-and-controller-di
C9S:feat/E11-stol-zakazov-frontend-and-tests
C9S:feat/E11-stol-zakazov-final
C9S:feat/capital-artifacts-access
C9S:fix/floor-asset-display
C9S:feat/epic-9-warehouse-reporting
C9S:feat/598-11-epic-8-writeoff
C9S:feat/E7-garantiynyy-vozvrat
C9S:design
C9S:fix/capital-revert-394-generation-convert-refs
C9S:fix/is-unioned-zod-string-parse
C9S:fix/capital-rename-generation-convert-to-money-invest
C9S:feat/E6-vydacha-payshchiku
C9S:worktree-remove-1081-1082-rename-1080
C9S:feat/init-by-server-from-provider
C9S:feat/E5-postavka-priemka
C9S:feat/E11-techdebt-L12-split-payout
C9S:feat/E4-zakaz
C9S:gh-pages
C9S:feat/E3-vitrina-canon
C9S:feat/E11-S1-ledger2-marketplace-ops
C9S:file-storage
C9S:feat/1-10-marketplace-registration-offer-status
C9S:feat/1-9-marketplace-board-acceptance
C9S:feat/1-8-marketplace-access-matrix
C9S:feat/1-7-marketplace-template-registry
C9S:feat/1-6-marketplace-role-guard
C9S:feat/1-5-marketplace-member-wallet
C9S:feat/1-4-marketplace-onboarding-gate
C9S:feat/1-3-marketplace-access-by-membership
C9S:feat/1-1-install-from-catalog
C9S:ci/lock-tags-only
C9S:onboarding
C9S:reports
C9S:ledger3
C9S:graph-standarts
C9S:feat/ledger-double-entry
C9S:feat/docs-enrichment
C9S:feat/documentation
C9S:feat/marketplace-orders
C9S:feat/interops-extension-system
C9S:cursor/development-environment-setup-2882
C9S:feat/unified-test-pipeline
C9S:fix/security-vulnerabilities
C9S:audio-stream
C9S:project-authorization-on-create
C9S:refactoring
C9S:capital
C9S:element
C9S:ledger
C9S:marketplace
C9S:notifications2
C9S:notifications
C9S:document2
C9S:TSK-900
C9S:TSK-878
C9S:dacom-dark-sun/sprint-4
C9S:uniconstructor/sprint-5
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.
Delete Branch "parser2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Интеграционная ветка sync-arch: перевод синхронизации controller с parser1 на единый движок parser2 ParserClient.
Состав (эпики)
Объём
71 файл, +3748/−851. Основное — components/controller/src/shared/sync (fork-registry, base-blockchain repo, entity-versioning, archive-retention) + покрытие тестами (unit + integration fork-flow).
ВНИМАНИЕ
Ветка отстаёт от dev на ~505 коммитов (ответвлена давно). Перед мержем нужно влить dev в parser2 и разрулить конфликты. PR открыт для ревью/видимости.
Stories 1.3 + 1.4 затрагивают общие файлы (config.ts, blockchain-consumer.service.ts), поэтому одним коммитом: - 1.4: ACTION_EMIT_DELAY_MS=3000 → config.blockchain.action_emit_delay_ms (env BLOCKCHAIN_ACTION_EMIT_DELAY_MS). Порядок save→ACK→setTimeout(emit) уже был корректен. - 1.3: EventsService.emitAsyncWithTimeout + await в processFork = пауза consumer'а до завершения @OnEvent('fork::*')-откатов; TTL force-resume = config.blockchain.fork_pause_timeout_ms. Временно до Epic 3/ForkRegistry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Цель — снять параллельный @OnEvent('fork::*') broadcast в 20+ syncer-ов и заменить на sequential обход через ForkRegistryService, что в сочетании с single-active XREADGROUP parser2 даёт натуральный барьер форка (INV-T03, NFR10). Старый broadcast путь оставлен deprecated на этот релиз — Story 4.2 удалит вместе с pause-barrier из DEC-013. Реализация: - shared/sync/fork/{interface,service,module}: IForkAwareSyncer + symbol-marker FORK_AWARE_MARKER, ForkRegistryService с pull-сбором через DiscoveryService на onApplicationBootstrap, sequential for-of await + priority-ordering. - AbstractEntitySyncService: implements IForkAwareSyncer (marker через class field), handleFork теперь re-throw (catch удалён) — контракт sequential ForkRegistry.runAll. Не требует super.onModuleInit() у 20 наследников (DiscoveryService снимает push-зависимость). - BlockchainConsumerService.handleEvent для fork: dedup-gate через computeForkEventId → processFork (runAll → deleteDedupAfterBlock → saveFork → deprecated emit) → markEventApplied. Для action/delta — markEventApplied теперь пишет block_num. - ConsumerDedup: новая колонка block_num (bigint nullable) + индекс + deleteAfterBlock(blockNum). Старые NULL-записи не затрагиваются. - event-id.util: новый computeForkEventId в формате chain:fork:block:short_id (полное «fork», единый стиль с action/delta — parser2-формат chain:f:... не используется). Тесты (51 кейс, 5 suites): - unit fork-registry: sequential apply, error propagation, priority, bootstrap discovery (15 кейсов). - unit blockchain-consumer.processFork: порядок шагов, dedup-gate, mark после processFork, fail-paths, parser2-format isolation (11 кейсов). - unit consumer-dedup repository: markApplied с/без blockNum, deleteAfterBlock edge-cases (8 кейсов). - unit event-id.util: computeForkEventId детерминизм + поведение на коротком block_id (5 новых кейсов). - integration fork-flow: bootstrap → delta(N+1)→fork(N)→delta(N+2), rollback не трогает <=N, error propagation (4 кейса с реальным Nest DI). tsc 0 ошибок, jest 51/51 зелёные. Spec: blago/production/13-platforma-tsifrovogo-kooperativa/components/14-versiya-3/_bmad-output/tracks/sync-arch/implementation-artifacts/spec-4-1-fork-kak-event-v-unified-stream.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>После Story 4.1 ForkRegistry уже даёт sequential rollback всех syncer'ов через pull-DiscoveryService, а saveFork идёт прямым вызовом из processFork. Старый broadcast-путь через EventEmitter ('fork::*' + emitAsyncWithTimeout с TTL force-resume из Story 1.3 DEC-013) — рудимент: дублирует saveFork (через BlockchainEventHandlerService.handleForkEvent), повторно зовёт handleFork в каждом syncer'е (idempotent no-op после ForkRegistry, но лишний DB-traffic). Удалено: - @OnEvent('fork::*') + handle*Fork методы в 20 файлах: · 3 в infrastructure/database/typeorm/blockchain/services/ (user-agreement, user-wallet, agreement) · 16 в extensions/capital/application/syncers/ · 1 в extensions/chairman/infrastructure/blockchain/services/ (approval) · 1 дубль saveFork в domain/parser/services/blockchain-event-handler.service.ts · orphan dead-метод в application/wallet/services/program-wallet-sync.service.ts - Шаг 4 (deprecated emitAsyncWithTimeout) из BlockchainConsumerService.processFork. - BLOCKCHAIN_FORK_PAUSE_TIMEOUT_MS из config/config.ts (zod + config объект). - Упоминание env-var из controller/CLAUDE.md. Что осталось: - EventsService.emitAsyncWithTimeout — определение в infrastructure/events/events.service.ts. Каллеров в src/ нет; не удаляю утилиту — возможно пригодится для Epic 5 (waitForDelta / pool retry). Удалят на следующем рефакторе если не использована. - AbstractEntitySyncService.handleFork остаётся: его теперь зовёт только ForkRegistry. - @OnEvent('action::*') и @OnEvent('delta::*') в BlockchainEventHandlerService — валидный dispatch pipeline (ADR-002), не трогаем. Tests: - processFork.test.ts: убраны expect'ы на emit; новый regression test что events.emit/emitAsyncWithTimeout НЕ вызываются с fork:: префиксом. - НОВЫЙ tests/unit/blockchain/no-onevent-fork.test.ts: grep-guard по src/ на @OnEvent('fork::*') (комментарии отфильтрованы) → fail на регрессии. - Все 6 suites зелёные (52 теста), tsc --noEmit 0 errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Audit-проход без миграции рантайма. Полная карта entity/repo и сравнений block_num — в blago _bmad-output/tracks/sync-arch/.../audit-report-4-3.md. Изменения в коде: 1. tests/unit/blockchain/base-blockchain-repository.contract.test.ts — НОВЫЙ regression-guard. Сканирует все *.typeorm-entity.ts extends BaseTypeormEntity и для каждой ищет *.typeorm-repository.ts extends BaseBlockchainRepository. Allowlist OFF_CHAIN_BASE_ENTITIES для 5 off-chain артефактов (comment, cycle, issue, story, time-entry) — у них block_num унаследован vestigial, синкера нет, форк их не откатывает (корректно). Тест ловит регрессию, если кто-то добавит блокчейн-mirror entity без BaseBlockchainRepository — иначе entity_versions молча перестанут писаться, форк превратится в hard delete (silent data loss). 2. controller/CLAUDE.md — два новых правила: (a) разнобой типов block_num: BaseTypeormEntity (capital+shared) использует integer+number — корректно; ActionEntity/DeltaEntity/ForkEntity/ SyncStateEntity — bigint+number type-mismatch (PG возвращает string, TS говорит number). Hot-path везде явно Number() либо PG bind, так что runtime safe. Технический долг в Epic 9 — bigint Transformer. (b) контракт «entity с block_num → repo extends BaseBlockchainRepository» + ссылка на regression test + allowlist OFF_CHAIN_BASE_ENTITIES. 3. tests/unit/blockchain/base-blockchain-repository.contract.test.ts: нюанс — entityKindFromFileName нормализует суффикс -typeorm (chairman approval-typeorm.entity.ts vs approval.typeorm-repository.ts mismatch имён файлов). Findings (полное в audit-report-4-3.md): - 25 entity extends BaseTypeormEntity; 20 имеют BaseBlockchainRepository. - 5 без BaseBlockchainRepository — off-chain артефакты (легитимно). - 4 infra entity с bigint+number type-mismatch (runtime safe, тех. долг). - Direct typeormRepo.save для блокчейн-зеркал — НЕ найдено (контракт соблюдён). - Все сравнения block_num в sync-core используют Number() или PG bind. Tests: 2/2 contract-test зелёные. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>AbstractBlockchainDeltaMapper получает protected readonly signedDocumentFields ReadonlyArray<SignedDocField> (default []) + helper normalizeSignedDocuments(data). SignedDocField описывает путь к IChainDocument2-полю в TBlockchainData; bracket- нотация поддерживает массивы: `appendix` (top-level), `statement.attachments[] .signed_attachment` (nested-array, E12). parseSignedDocPath → PathSegment[], applyAtPath in-place трансформирует через DomainToBlockchainUtils.convertChainDocumentToDomainFormat. appendix-delta.mapper.ts — эталон: ручной convertChainDocumentToDomainFormat (value.appendix) заменён декларацией signedDocumentFields = [{ path: 'appendix' }] + this.normalizeSignedDocuments({ ...value }). signed-document-normalization.test.ts (16) — парсер 4 кейса + normalize 7 кейсов (top-level, nested-array, missing-field no-op, multiple fields, empty config, null/undefined, immutability caveat). delta-mapper-signed-doc.contract.test.ts (3) — guard: mapper с непустым signedDocumentFields обязан вызвать this.normalizeSignedDocuments — иначе silent data corruption (meta остаётся JSON-строкой, Story 6.4 checksum не сходится). IPFS lazy resolver (E14) и миграция остальных mapper'ов с ручной нормализацией — вне scope, тречится Epic 9. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Новые shared/sync/errors: - UnsupportedContractVersionError(entityName, ctx{contract,table,primary_key, block_num}) — носит контекст для DLQ-операторской диагностики. - auditUnknownStatus(entityName, receivedStatus, logger, allowedStatuses?) — фиксирует unknown-status drift в audit-trail с ожидаемыми вариантами. AbstractEntitySyncService.processDelta: mapper вернул null → logger.error ("UNSUPPORTED_CONTRACT_VERSION", ctx) ВСЕГДА (раньше был silent warn). В strict-mode (config.blockchain.unsupported_version_strict=true) дополнительно throw — парсер не ACK'нет дельту, DLQ сработает. UnsupportedContractVersionError из try/catch пробрасывается дальше; остальные ошибки по-прежнему логируются и return null. config.ts: BLOCKCHAIN_UNSUPPORTED_VERSION_STRICT (default false) → blockchain .unsupported_version_strict. Default false — не ломать прод немедленно; включается на стенде после подтверждения отсутствия schema drift. ProjectDomainEntity.mapStatusToDomain эталонно — default ветка вместо silent UNDEFINED-возврата вызывает auditUnknownStatus с полным списком ожидаемых: [pending,active,voting,result,finalized,cancelled]. Остальные mapStatusToDomain (state, vote, segment, debt и т.д.) — Epic 9.5. unsupported-version-explicit-error.test.ts (6) — UnsupportedContractVersionError конструктор/контекст; auditUnknownStatus с/без allowedStatuses; processDelta non-strict (logger.error + null), strict (throw), happy-path (handleSyncDelta вызывается). 229/229 blockchain unit зелёные. Эпик 6 закрыт целиком: 6.1 namespace, 6.2 normalizeSignedDocuments, 6.3 Zod schemas, 6.4 checksum, 6.5 explicit errors. Release 1.1.2 движется. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Story 6.3 (per-doc-type zod schemas): валидация `signatures.length === 1/2` зашивала документную семантику в нормализатор parser-event. Число и валидность подписей — домен контракта (`require_auth`), не бэкенда. При расширении доктайпов контракта (1→2 подписи) sync падал бы на структурно-валидной цепи. Структурный transform `meta: JSON-string → object` (Story 6.2) остаётся. Story 6.4 (`_checksum` колонка + sha256(canonical-json(bc))): потребитель — Epic 7 nightly snapshot и Epic 8 reconciliation — ещё не реализованы. Колонка хранила бы пустую нагрузку + +20% storage + лишний sha256 на каждом save/update. Принцип «не добавлять контрольных полей до появления потребителя». Алгоритм canonicalStringify заведём как часть Epic 8. Удалено: src/shared/sync/{checksum.util.ts, signed-document-schemas.ts} + 3 теста + applyBcChecksum + колонка _checksum + поле _checksum на BaseDomainEntity. Story 6.1 (namespace db/bc + replaceBc), 6.2 (declarative signedDocumentFields + meta transform), 6.5 (UnsupportedContractVersionError + auditUnknownStatus) сохраняются. 13 jest blockchain unit suites / 196 tests зелёные. tsc зелёный. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.