[h1-request] 同期v2: 集約ストアと push/pull(1伝票単位・rev・冪等) #54
Labels
No labels
h1-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
joe/o2#54
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
背景
v1 の
SyncRecordは(device_id, entity_type, uuid)単位のミラーで、o2 自身の編集や複数端末による同一伝票の編集を表現できない。伝票と明細が別エンティティで届くため、1伝票の整合も保証できない。依頼内容(ADR-0002 §3〜§6・§11)
(tenant_id, entity_type, uuid)単位で1件。集約ごとに単調増加のrevを o2 が採番。起源端末・最終更新端末を記録POST /api/sync/v2/push: v2 バンドル(§4。伝票は本体+明細+関連+入金消込を1バンドル)を受理(device_id, entity_type, uuid, hash)受理済みならduplicatebase_rev == current_rev→accepted(rev)base_rev < current_rev→ HLC 比較で後勝ち(負けたらstale_overridden+o2 版)base_rev < current_rev→conflict(受信版は保存、現行版は変えない。合議は別 Issue)rejected_locked(ロック解除も含め一切受け付けない)rejected_fiscal(判定値は締め日の Issue で提供。先行実装時は常に通過でよい)SyncProjectionService/H1AccountingService)を実行GET /api/sync/v2/pull?since=:kind=aggregate(テナント内の他端末・o2 起源の更新)。自端末が送って受理済みの集約は rev 通知のみ受け入れ条件
契約の正本: ADR-0002(ブランチ
docs/adr-0002-offline-first-sync-v2。未マージなら先に main へマージしてから着手)https://git.cyberius.biz/joe/o2/src/branch/docs/adr-0002-offline-first-sync-v2/docs/adr/0002-offline-first-sync-v2.md
h1 側の内部設計(参考): https://git.cyberius.biz/joe/h1-c2/src/branch/main/docs/superpowers/specs/2026-10-09-offline-first-sync-design.md
前提: h1 は本体単体で完全稼働し、o2 接続時のみ同期する。v1(
/api/sync/push|pull・/api/sync/corrections*)は h1 の移行完了まで並行稼働を維持すること(v1 を壊さない)。検証データ: 入出力例を
tests/fixtures/sync_v2/*.jsonに追加する。h1 は同じ fixture を Dart テストで検証する(sales_calculationfixture と同じ運用)。実装完了(o2側)— 集約ストアと push/pull
ADR-0002 を main にマージ(
6107637)し、Issue #54 を実装しました。全 6358 passed。実装
SyncAggregate((tenant_id, entity_type, uuid)一意、rev採番、data(header/items/relations/allocations/materials)、hash、origin_device_id/last_device_id、doc_date、locked、deleted、hlc)SyncInboxV2((device_id, entity_type, uuid, hash))POST /api/sync/v2/push。判定マトリクス(duplicate/rejected_fiscal(フック)/rejected_locked/accepted/stale_overridden(作業データHLC後勝ち)/conflict(伝票・記録系)/master_decision(フック=当面 accepted))。発行ロックは発行系・受領系のfalse→trueのみ受理、解除は不受理。accepted 時に射影/会計射影を実行GET /api/sync/v2/pull?since=(SyncV2Eventの単調増加 id をカーソル、kind=aggregate。他 kind は枠)/api/sync/push|pull・corrections)は不変。v1→v2 移行サービス+CLI(scripts/migrate_sync_v1_to_v2.py、明細は親伝票へ統合)tests/fixtures/sync_v2/*.json(各判定の入出力例・v1移行)d5e6f7a8b9c0(syncaggregate/syncinboxv2/syncv2event/syncconflict)引き継ぎフック(後続 Issue)
SyncConflictに受信版保存済み(解決APIは未実装)master_decision()→ 当面 acceptedis_fiscal_locked()/has_lock_exception()→ 当面 False注意(インフラ)
www.cyberius.biz:18)。実装はローカル main(7c50050)にマージ済み・o2.db適用済みで、push は回復後に実施します。