sync-arch: явные ошибки при unknown contract version / status (Story 6.5) [C28-16] #57
Reference in New Issue
Block a user
Delete Branch "parser2-epic-6-canonical-storage"
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?
Что в PR (после перегруппировки 2026-06-02)
Изначально этот PR содержал весь Эпик 6 sync-arch (5 stories). После обсуждения с командой эпик переразложен: цель MVP-релиза 1.1.2 — заменить транспорт parser1→parser2 без поломки прода. Архитектурная санитация sync-слоя — отдельная задача, в этот релиз не идёт.
В PR остался только Story 6.5 — очевидный фикс silent loss при schema drift, не инвазивный.
Story 6.5: явные ошибки при unknown contract version / unknown status
Раньше при
mapper.mapDeltaToBlockchainData(delta) === null(контракт обновили, контроллер не знает версию) —warn+return null→ парсер ACK'ал дельту → потеря события молча. АналогичноmapStatusToDomainвозвращалUNDEFINEDбез сигнала.Что добавлено:
UnsupportedContractVersionError(src/shared/sync/errors/unsupported-contract-version.error.ts) — кастомный класс с контекстом (contract/table/primary_key/block_num).auditUnknownStatus(entityName, status, logger, allowedStatuses)(src/shared/sync/errors/audit-unknown-status.ts) — helper для error-log.AbstractEntitySyncService.processDelta: приblockchainData === null→logger.error('UNSUPPORTED_CONTRACT_VERSION', ctx); в strict-modethrow UnsupportedContractVersionError→ парсер не ACK'нет дельту, dead-letter сработает.ProjectDomainEntity.mapStatusToDomain: default ветка вызываетauditUnknownStatus(...)перед fallback на UNDEFINED.BLOCKCHAIN_UNSUPPORTED_VERSION_STRICT(defaultfalse) — не ломаем прод сразу, оператор может включить.Что НЕ вошло (откатано в этом PR ревизионными коммитами)
db/bc/derived+replaceBc+ переписанный ProjectDomainEntity) — двойной канон на 22 entity, отложено в отдельный sync-arch sanitation-эпик.signedDocumentFields+normalizeSignedDocuments+ path-parser) — DRY-рефакторинг без функциональной выгоды на текущем объёме.signatures.length === 1/2) — документная семантика на бэкенде неправильно, число подписей — домен контракта (require_auth)._checksumколонка + sha256(canonical-json(bc))) — без потребителя (Эпик 7 nightly snapshot + Эпик 8 reconciliation тоже отложены) = мёртвая колонка + +20% storage + лишний sha256 на каждое save/update.Stacked-PR
parser2-epic-4-forks(PR #54 наparser2, ждёт merge).parser2.Test plan
Дополнительные документы
_bmad-output/tracks/sync-arch/planning-artifacts/epics.md→ раздел «Перегруппировка плана 2026-06-02»._bmad-output/tracks/sync-arch/planning-artifacts/MVP-parser2-vs-deferred.md.🤖 Generated with Claude Code
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>Эпик 6 sync-arch: единый порядок хранения данных и подписанных документов (релиз 1.1.2) [C28-16]to sync-arch: явные ошибки при unknown contract version / status (Story 6.5) [C28-16]