Skip to content

tso: add dedicated ceiling fsm - #1095

Open
bootjp wants to merge 21 commits into
mainfrom
design/dedicated-tso-fsm
Open

tso: add dedicated ceiling fsm#1095
bootjp wants to merge 21 commits into
mainfrom
design/dedicated-tso-fsm

Conversation

@bootjp

@bootjp bootjp commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a minimal dedicated TSO state machine that accepts only HLC lease entries.
  • Snapshot and restore the physical ceiling as 8-byte big-endian state, and classify full lease entries as volatile-only.
  • Update the centralized TSO design doc status and remaining runtime wiring.

Validation

  • go test ./kv -run 'TestTSOStateMachine|TestLocalTSOAllocator|TestBatchAllocator|TestShardedCoordinator.*Timestamp|TestCoordinateUsesTSOAllocator' -count=1 -timeout=180s
  • go test ./kv -count=1 -timeout=300s
  • golangci-lint --config=.golangci.yaml run ./kv --timeout=5m
  • git diff --check
  • go test ./adapter -run '^TestMilestone1SplitRange_RestartReloadsCatalog$' -count=1 -timeout=180s
  • go test ./... -count=1 -timeout=600s (adapter package timed out at 600s; other packages completed)

Notes

  • This adds the dedicated FSM implementation and tests. Runtime bootstrap wiring for groupID = 0 remains a follow-up until the TSO leader redirect path exists.

Author: bootjp

Summary by CodeRabbit

  • 新機能
    • 専用TSOグループによる時刻の割り当て、リーダー経由の要求処理、カットオーバーおよびPhase Dに対応しました。
    • タイムスタンプの予約・検証APIを追加しました。
    • スナップショットの保存・復元に対応し、従来形式のデータも互換的に復元できます。
  • 改善
    • 各種トランザクションで一貫した読み取り時点を利用し、処理の整合性を高めました。
    • 専用TSOグループでは暗号化変更操作を安全に拒否し、サービスを継続できるようになりました。
    • グループ構築時のエラー処理とリソース解放を改善しました。
  • ドキュメント
    • TSO移行の進捗、互換性、今後の対応範囲を更新しました。

@bootjp

bootjp commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

専用TSOのRaft予約、TSO FSMの永続状態、Phase Dのtimestamp検証、ReadTimestamp voucherを追加した。Redis、DynamoDB、S3、SQS、Distributionの書き込み経路を同じ読み取り時点で実行するよう更新した。専用TSO groupの起動配線とEncryptionAdmin制御も更新した。

Changes

TSO状態、予約、Phase D検証

Layer / File(s) Summary
TSO FSMとRaft予約
kv/tso_fsm.go, kv/tso_raft.go, kv/tso.go, kv/shard_store.go
allocation floor、cutover、Phase D状態、commit floor、リーダールーティング、shadow allocatorを追加した。v1〜v4および旧kvFSM snapshotの復元を実装した。
CoordinatorのStartTS検証とvoucher
kv/sharded_coordinator.go, kv/coordinator.go, kv/keyviz_label.go
ReadTimestamp voucherの登録・消費・取消しを追加した。Phase Dではcross-shardのStartTSを永続状態で検証し、単一シャードでは既存のStartTSを保持する。
TSO検証テスト
kv/tso_fsm_test.go, kv/tso_raft_test.go, kv/tso_floor_test.go, kv/sharded_coordinator_txn_test.go, kv/tso_test.go
予約失敗、term変更、floor不整合、snapshot互換性、voucher競合、Phase D境界を検証するテストを追加した。

RuntimeとRPC配線

Layer / File(s) Summary
専用TSO groupと起動配線
main.go, main_tso_routing_test.go, multiraft_runtime_test.go
専用TSO groupではMVCC storeを開かず、TSO FSMとRaft runtimeを構築する。設定に応じてlocal、shadow、leader-routed、Phase D allocatorを選択する。
Distribution契約とcatalog snapshot
proto/distribution.proto, proto/service.proto, adapter/distribution_server.go, distribution/catalog.go
GetTimestampの予約・cutover・Phase D情報を拡張した。ValidateTimestamp RPCとgroup単位のcommit floor応答を追加した。SplitRangeはReadTimestamp付きcatalog snapshotを使用する。
EncryptionAdmin制御
main_encryption_admin.go, main_encryption_admin_test.go
専用TSO groupではmutator配線を無効化し、LeaderViewを維持する。mutator RPCはFailedPreconditionを返す。

アダプターの読み取り時点統一

Layer / File(s) Summary
Redis transaction paths
adapter/redis_*.go
SET、DEL、hash、list、set、stream、zset、Lua、TTL、EXEC、reaper、compactorをReadTimestampとvoucher付きdispatchへ移行した。dedup retryではReadTimestampを再利用する。
DynamoDB、S3、SQS transaction paths
adapter/dynamodb_*.go, adapter/s3*.go, adapter/sqs_*.go
読み取りと書き込みに同じReadTimestampを使用する。commit dispatchをDispatchWithReadTimestampへ変更した。
Adapter validation tests
adapter/*_test.go
Phase D watermark、voucher伝播、再利用dispatch、アップロード再検証、commit floor、専用TSOの動作を検証する。

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Adapter
  participant Coordinator
  participant TimestampAllocator
  participant ShardedStore
  Adapter->>Coordinator: BeginReadTimestampThrough
  Coordinator->>TimestampAllocator: allocate or validate timestamp
  TimestampAllocator->>ShardedStore: verify durable floor
  ShardedStore-->>TimestampAllocator: validated watermark
  TimestampAllocator-->>Coordinator: ReadTimestamp and voucher
  Coordinator-->>Adapter: read timestamp
  Adapter->>Coordinator: DispatchWithReadTimestamp
  Coordinator->>ShardedStore: dispatch transaction with StartTS
Loading

Possibly related PRs

  • bootjp/elastickv#748: TSO FSMで拒否するレガシー暗号制御エントリのwire opcodeとHaltApply処理に関連する。
  • bootjp/elastickv#1064: 専用TSO group-0のルーティング、起動配線、リーダー動作に関連する。
  • bootjp/elastickv#1150: TSOStateMachineのceiling状態をallocation floor、cutover、Phase D、互換snapshotへ拡張する変更に関連する。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは専用TSOのceiling FSM追加を示し、変更内容の一部に関連していますが、PR全体の主な変更範囲は十分に表していません。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the TSOStateMachine, a minimal state machine for the dedicated timestamp group that handles HLC lease-renewal entries, along with its snapshotting, restoration, and volatile entry classification logic. It also updates the design documentation to reflect this progress and adds comprehensive unit tests for the new state machine. There are no review comments to evaluate, and I have no additional feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

| M6 — partial | Dedicated TSO Raft group (`groupID = 0`) is reserved/bootstrap-capable and warmed by the HLC renewal bridge; TSO-leader-only timestamp issuance and the minimal `TSOStateMachine` remain open | Low |

P3 Badge Update stale M6 status row

This milestone row still says the minimal TSOStateMachine remains open, but this same change marks it implemented in §1.0 and adds kv/tso_fsm.go. Readers using this design doc to plan M6 will incorrectly think the FSM work is still pending, so please update the row to match the new runtime-wiring-only remaining status.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_fsm.go Outdated
}
ceilingMs := int64(binary.BigEndian.Uint64(data[1:])) //nolint:gosec // value is a Unix ms timestamp encoded as uint64.
if f != nil && f.hlc != nil && ceilingMs > 0 {
f.hlc.SetPhysicalCeiling(ceilingMs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Advance the TSO HLC floor on lease apply

When this FSM is used for the dedicated TSO group, applying a lease only updates physicalCeiling while leaving HLC.last at zero on followers/restarted nodes. If the current TSO leader has handed out timestamps under a future ceilingMs and then fails before the wall clock reaches it, the new leader has applied the same lease but has not observed any of those allocations, so its first NextBatchFenced can return (ceilingMs<<16)|0 again and duplicate timestamps. The TSO path needs to persist/observe an allocation floor or force a fresh higher lease before serving after leadership changes.

Useful? React with 👍 / 👎.

Comment thread kv/tso_fsm.go Outdated

func (f *TSOStateMachine) Apply(data []byte) any {
if len(data) != hlcLeaseEntryLen {
return errors.Wrapf(ErrTSOStateMachineInvalidEntry, "expected %d bytes, got %d", hlcLeaseEntryLen, len(data))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return a halting response for invalid TSO entries

If a malformed payload is ever committed to the dedicated TSO group, returning a bare error here does not actually reject it at the raft apply layer: applyNormalCommitted only stops before setApplied for responses implementing HaltApply() error, while plain error responses are still marked applied and only delivered back to the proposer. In a misrouted group-0 proposal or mixed-version entry scenario, the invalid entry would be permanently advanced past instead of failing closed, violating the FSM's “accepts only HLC lease entries” contract.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated commit: 4e49dfbe7f31f084fc98bb5bec47a0e1458c4c24

Addressed current-head review findings:

  • TSOStateMachine now observes a derived allocation floor for each applied/restored HLC lease ceiling, preventing a restarted or newly elected TSO leader from reissuing the previous leader's logical slots.
  • Invalid TSO FSM entries now return a HaltApply response instead of a plain error, so malformed group-0 entries fail closed without advancing applied index.
  • The centralized TSO design doc M6/status text now matches the implemented FSM and remaining runtime-wiring work.

Caller/semantic audit:

  • NewTSOStateMachine remains only used by the new tests in this branch; runtime group-0 wiring is still a documented follow-up.
  • The HLC floor change is limited to the dedicated TSO FSM path and does not change kvFSM HLC lease apply behavior.

Validation:

  • go test ./kv -run 'TestTSOStateMachine|TestLocalTSOAllocator|TestBatchAllocator|TestShardedCoordinator.*Timestamp|TestCoordinateUsesTSOAllocator' -count=1 -timeout=180s
  • go test ./kv -count=1 -timeout=300s
  • golangci-lint --config=.golangci.yaml run ./kv --timeout=5m
  • git diff --check
  • git verify-commit HEAD

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e49dfbea8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_fsm.go Outdated
return
}
hlc.SetPhysicalCeiling(ceilingMs)
hlc.Observe(tsoLeaseAllocationFloor(ceilingMs))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist the post-lease allocation floor

When the dedicated TSO FSM is wired, applying/restoring a lease with this Observe only records ceilingMs|maxLogical in memory while snapshots/WAL entries still persist just ceilingMs. If a leader applies ceiling C, serves one timestamp, NextBatchFenced returns (C+1,0) because the floor is already C|maxLogical; after a restart or re-election with only the same lease entry/snapshot, applyTSOLeaseToHLC recreates the same floor and can serve (C+1,0) again. This means the new floor does not actually prevent reissuing timestamps unless the allocation floor/current HLC is made durable or allocation is blocked until a higher ceiling is committed.

Useful? React with 👍 / 👎.

Comment thread kv/tso_fsm.go Outdated
Comment on lines +72 to +73
if hlc == nil || ceilingMs <= 0 {
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-positive TSO lease ceilings

When a full HLC-lease entry encodes 0 or a high-bit value that decodes negative, this branch returns without error, so Apply returns nil and the engine advances the applied index. Renewals only generate positive Unix-ms ceilings, so these are malformed group-0 lease entries; accepting them masks the bad proposal instead of using the new fail-closed HaltApply path. Please reject non-positive ceilings in Apply while keeping snapshot restore's empty-state handling separate if needed.

Useful? React with 👍 / 👎.

Comment thread kv/tso_fsm.go Outdated
if f != nil {
hlc = f.hlc
}
return &tsoFSMSnapshot{ceilingMs: hlcCeilingFromHLC(hlc)}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep snapshots sourced from TSO-applied leases

When group 0 is wired during the documented bridge, this FSM is likely to share the process HLC that shard FSMs also advance; then Snapshot can serialize a ceiling raised by some data-group lease even if the TSO group never committed that lease. If a shard renewal succeeds while the group-0 proposal fails or lags, installing/restoring the group-0 snapshot advances dedicated TSO state beyond its own consensus log. Keep a TSO-FSM-owned ceiling field updated only from this FSM's Apply/Restore path and snapshot that value instead.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated commit: 44e6c18

Addressed current-head review findings:

  • TSOStateMachine now keeps TSO-owned ceiling and allocation-floor state, and snapshots those fields instead of reading the shared HLC mirror. This prevents data-group lease renewals from contaminating group-0 snapshots.
  • HLC lease entries now advance only the physical ceiling; allocation-floor advancement is an explicit TSO FSM entry and is persisted through the FSM snapshot format.
  • Non-positive HLC lease ceilings and malformed allocation-floor entries now halt apply through ErrTSOStateMachineInvalidEntry.
  • The design doc now reflects the ceiling/floor snapshot contract and the remaining runtime group-0 wiring work.

Caller/semantic audit:

  • NewTSOStateMachine is still referenced only by this branch's TSO FSM tests; runtime group-0 wiring remains documented as a follow-up.
  • kvFSM HLC lease behavior is unchanged; the new allocation-floor opcode is TSO-FSM-local and intentionally uses a byte that data-group kvFSM already routes through the fail-closed reserved range.

Validation:

  • go test ./kv -run 'TestTSOStateMachine|TestLocalTSOAllocator|TestBatchAllocator|TestShardedCoordinator.*Timestamp|TestCoordinateUsesTSOAllocator' -count=1 -timeout=180s
  • go test ./kv -count=1 -timeout=300s
  • golangci-lint --config=.golangci.yaml run ./kv --timeout=5m
  • git diff --check
  • git verify-commit HEAD

Additional check:

  • go test ./... -count=1 -timeout=600s was attempted; all completed packages reported ok, but adapter timed out at 600s in long-running gRPC/SQS adapter tests. This is outside the changed kv/doc surface.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44e6c1852b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_fsm.go Outdated
Comment on lines +104 to +105
case tsoSnapshotV1Len:
ceilingMs = int64(binary.BigEndian.Uint64(payload[:hlcLeasePayloadLen])) //nolint:gosec // legacy snapshot value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive the floor when restoring legacy TSO snapshots

When this restores an 8-byte snapshot emitted by the previous TSO FSM format, allocationFloor remains 0. Those legacy snapshots were produced by code that also observed tsoLeaseAllocationFloor(ceilingMs) on apply/restore to prevent a restarted or newly elected TSO leader from handing out logical slots from the old ceiling millisecond. After upgrading from such a snapshot, the HLC mirror has only the ceiling, so the next NextBatchFenced can start at ceiling<<16|0 and overlap timestamps already covered by the prior floor; derive the legacy floor from the decoded ceiling before calling restoreSnapshotState.

Useful? React with 👍 / 👎.

Comment thread kv/tso_fsm.go Outdated
Comment on lines +150 to +151
f.ceilingMs.Store(ceilingMs)
f.allocationFloor.Store(allocationFloor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep TSO-owned fields monotonic during restore

When Restore is called on an FSM that has already applied a higher ceiling/floor (the monotonic-restore test exercises this shape), these unconditional stores regress the FSM-owned state even though the HLC mirror remains high via its monotonic setters. Because Snapshot() now serializes f.ceilingMs and f.allocationFloor rather than the HLC, the next snapshot from that FSM can persist the lower values and lose the higher applied TSO floor/ceiling; restore should max these fields the same way apply does.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated commit: 5b6fd30

Addressed current-head P2 findings:

  • Restoring a legacy 8-byte TSO FSM snapshot now derives the allocation floor from the restored ceiling, preserving the previous format's floor behavior across upgrade/restart.
  • Restore now updates TSO-owned ceiling/floor fields monotonically instead of unconditionally storing lower snapshot values; the HLC mirror and subsequent snapshots stay at the highest applied TSO state.
  • The design doc Restore example now reflects the 8-byte legacy and 16-byte current snapshot handling.

Caller/semantic audit:

  • NewTSOStateMachine remains referenced only by this branch's TSO FSM tests; runtime group-0 wiring remains a documented follow-up.
  • The restore semantic change is limited to TSOStateMachine and matches the monotonic apply path.

Validation:

  • go test ./kv -run 'TestTSOStateMachine|TestLocalTSOAllocator|TestBatchAllocator|TestShardedCoordinator.*Timestamp|TestCoordinateUsesTSOAllocator' -count=1 -timeout=180s
  • go test ./kv -count=1 -timeout=300s
  • golangci-lint --config=.golangci.yaml run ./kv --timeout=5m
  • git diff --check
  • git verify-commit HEAD

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 5b6fd30121

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Base automatically changed from design/dedicated-tso-group to main July 19, 2026 06:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46be7a832f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread main.go Outdated
Comment on lines +1195 to +1196
sg := &kv.ShardGroup{Engine: runtime.engine}
sg.Txn = kv.NewLeaderProxyForShardGroup(sg, kv.WithProposalObserver(proposalObserver))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't count the TSO group in raft-envelope cutover scope

When group 0 is configured alongside a single data group, this returned ShardGroup is added to encWiring.attachRaftEnvelopeGroup in buildShardGroups. I checked adapter/encryption_admin.go's EnableRaftEnvelope path and main_raft_envelope_wiring.go's ValidateCutoverScope: the cutover precheck rejects unless exactly one group is attached, so enabling the dedicated TSO group turns a previously valid single-data-group raft-envelope rollout into FailedPrecondition (got 2) even though group 0 holds no user data. Exclude group 0 from that scope check or handle its cutover separately.

Useful? React with 👍 / 👎.

Comment thread kv/tso_fsm.go Outdated
Comment on lines +181 to +183
legacy, err := hasLegacyKVFSMSnapshotHeader(br)
if err != nil || !legacy {
return legacy, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve headerless legacy group-0 snapshots

When upgrading a reserved group-0 log that previously ran kvFSM from a version that emitted the supported headerless snapshot format, this probe returns legacy=false, so Restore falls through to readTSOSnapshotState and accepts only exactly 8 or 16 bytes. ReadSnapshotHeader deliberately leaves non-magic/headerless snapshots unconsumed for the inner store restore path, and those raw store payloads are not constrained to the TSO lengths, so affected nodes can fail to restore or misparse a 16-byte store payload instead of draining the legacy MVCC snapshot. Detect and drain headerless kvFSM snapshots before trying the raw TSO format.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e701cb221b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread main_encryption_admin.go Outdated
Comment thread kv/tso_fsm.go Outdated
@bootjp
bootjp force-pushed the design/dedicated-tso-fsm branch from e701cb2 to 97a42ba Compare July 19, 2026 12:17
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Evidence for current HEAD 97a42ba72c18205fcb84a43ad4ca0cedc48efa31:

  • Dedicated TSO encryption-admin wiring keeps mutators disabled but retains LeaderView; ResyncSidecar now rejects group-0 followers.
  • Allocation-floor proposals use a versioned TSO envelope. Bare/future encryption-reserved entries cannot be decoded as TSO state and halt fail-closed.
  • Semantic audit: the sole production encryptionAdminWiringForGroup caller was checked; data-group mutator behavior is unchanged. Allocation-floor encoding remains TSO-FSM-local in this PR and all apply/classifier/test consumers use the same envelope.
  • TLA audit: make tla-check matched all safe and expected-gap model outcomes.
  • The design remains Partial until the full dependency stack is clear.

History sanitation:

  • Rebuilt on current origin/main 915bc77795d940644e09ce3ee521626e64e2f442.
  • PR-visible history is one commit, authored and committed by bootjp <contact@bootjp.me>.
  • GitHub signature verification is verified: true.
  • Desired tree hash before and after rebuild: 5d4fd0dccfb27bf859311df68cdf9a119fcb33bc.

Validation:

  • go test ./kv . -count=1
  • go test -race ./kv . -run "TestTSOStateMachine|TestEncryptionAdmin_(DedicatedTSOGroup|DataGroup)|TestRegisterEncryptionAdminServer" -count=1
  • golangci-lint run ./... --timeout=5m --allow-parallel-runners (0 issues)
  • make tla-check
  • git diff --check origin/main..HEAD

@codex review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
kv/tso_fsm.go (1)

94-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

//nolint:gosec の抑制は境界チェックへの置き換えを検討してください。

コーディングガイドラインでは //nolint の追加を避け、リファクタリングを優先することが求められています。タイムスタンプの uint64int64 変換については、変換前に math.MaxInt64 との境界チェックを行う小さなヘルパー(例: func unixMillisToInt64(v uint64) (int64, error))を導入することで、複数箇所の //nolint:gosec を排除できます。既存コードで許容される慣例であれば据え置きで構いませんが、新規追加分については抑制の集約を推奨します。

As per coding guidelines: "Avoid adding //nolint unless absolutely required; prefer refactoring."

Also applies to: 165-167, 197-197, 325-325

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_fsm.go` at line 94, Replace the new `//nolint:gosec` suppressions
around the `ceilingMs` conversion and the corresponding conversions at the other
referenced sites with a shared checked conversion helper, such as
`unixMillisToInt64`. Have the helper validate against `math.MaxInt64` before
converting and return an error for overflow, then propagate or handle that error
at each caller while preserving existing timestamp behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@kv/tso_fsm.go`:
- Line 94: Replace the new `//nolint:gosec` suppressions around the `ceilingMs`
conversion and the corresponding conversions at the other referenced sites with
a shared checked conversion helper, such as `unixMillisToInt64`. Have the helper
validate against `math.MaxInt64` before converting and return an error for
overflow, then propagate or handle that error at each caller while preserving
existing timestamp behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 378b480f-a7cd-42e8-a4ec-e03b8e9fa899

📥 Commits

Reviewing files that changed from the base of the PR and between 915bc77 and 97a42ba.

📒 Files selected for processing (7)
  • docs/design/2026_04_16_partial_centralized_tso.md
  • kv/tso_fsm.go
  • kv/tso_fsm_test.go
  • main.go
  • main_encryption_admin.go
  • main_encryption_admin_test.go
  • multiraft_runtime_test.go

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 97a42ba72c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

bootjp added 2 commits July 19, 2026 22:58
## Summary
- route dedicated group-0 timestamp requests to the current TSO leader
- commit every returned window end and fence each leader term above
authoritative data-group commit floors
- add synchronous fail-closed shadow migration and a durable one-way
cutover marker
- preserve rolling compatibility by rejecting legacy timestamp responses
without durable reservation metadata
- update the centralized TSO design status through M6

## Migration safety
- shadow candidates are serialized through group 0 before the legacy
value is returned
- overlapping legacy candidates are discarded and retried
- cutover commits the marker before the first production window
- group-0, shadow, and cutover failures stop timestamp issuance instead
of falling back

## Validation
- `go test ./kv -count=1 -timeout=10m`
- `go test . -count=1 -timeout=10m`
- `go test ./adapter -run
'Test(DistributionServerGetTimestamp|GRPCServer_RawLatestCommitTS_EmptyKeyReturnsGlobalWatermark)'
-count=1 -timeout=5m`
- `go test ./... -run '^$' -count=1 -timeout=10m`
- `go test -race ./kv -run
'Test(RaftTSOAllocator|LeaderRoutedTSOAllocator|ShadowTimestampAllocator|ShardStoreGlobalCommittedTimestampFloor)'
-count=1 -timeout=10m`
- `golangci-lint --config=.golangci.yaml run ./kv ./adapter .
--timeout=5m`
- `make gen
BREAKING_AGAINST='../.git#subdir=proto,branch=design/dedicated-tso-runtime'`

## Stack
Base: #1103

Author: bootjp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa94f4eedb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_raft.go Outdated
Comment on lines +131 to +132
if min == ^uint64(0) {
return empty, errors.WithStack(ErrTxnCommitTSRequired)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject near-overflow TSO minimums

When ReserveBatchAfter is called with n > 1 and a minimum close to MaxUint64 (for example Distribution.GetTimestamp{count:2, min_timestamp:MaxUint64-1}), this guard lets the request through; a.clock.Observe(min) then causes NextBatchFenced to wrap the reserved base to 0 and the HLC current value to 1 before the later response validation can fail, and commitAllocationFloor can persist that regressed floor. Reject any min that cannot fit the requested window before observing it.

Useful? React with 👍 / 👎.

Comment thread main.go
cfg.engine,
distCatalog,
adapter.WithDistributionCoordinator(coordinate),
adapter.WithDistributionTimestampAllocator(tsoWiring.serverAllocator),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate mutating timestamp RPCs during startup rotation

When a client can reach the raft gRPC listener during startup rotation, this wiring makes Distribution.GetTimestamp call the dedicated allocator and commit group-0 allocation/cutover entries, but startupRotationGatedMethod only gates Distribution.SplitRange plus the RawKV/Transactional/Internal/Admin mutators. That lets timestamp proposals bypass the same startup mutator fence before waitRotateOnStartup completes and before public traffic is marked ready; include GetTimestamp in the gate or delay exposing the allocator until the gate opens.

Useful? React with 👍 / 👎.

Comment thread adapter/distribution_server.go Outdated
return &pb.GetTimestampResponse{Timestamp: s.engine.NextTimestamp()}, nil
}

reservation, err := s.allocateTimestampReservation(ctx, count, minTimestamp, activateCutover)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate TSO cutover activation

When group 0 is configured for Phase A/B but this node was not started with --tsoEnabled, any caller that can reach Distribution.GetTimestamp can set activate_cutover=true; this line forwards that untrusted request bit into the allocator, which commits the one-way cutover marker. Since main wires the server allocator even before production cutover, this bypasses the operator flag and the all-nodes-shadow rollout precondition, so keep activation tied to local/internal configuration rather than the public RPC field.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go Outdated
return 0, errors.Wrapf(ErrTSOCommitFloorUnavailable,
"data group %d is not led by this node", groupID)
}
if _, err := linearizableReadEngineCtx(nonNilTSOContext(ctx), engine); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound local floor ReadIndex calls

When the TSO leader also leads a data group and the incoming timestamp request has no deadline, this local LinearizableRead inherits an unbounded context, unlike verifyLeaderEngineCtx and the remote floor RPC timeout. If that ReadIndex stalls, ReserveBatchAfter is still holding the allocator mutex while initializing the term floor, so one stuck local floor probe can wedge all subsequent TSO allocations; wrap this path in a bounded context.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
bootjp added 7 commits July 23, 2026 20:54
Author: bootjp

## Summary
- persist a one-way Phase D marker and floor after the production
cutover marker
- stop data-group HLC renewal and legacy/shadow timestamp issuance once
Phase D is durable
- validate caller-supplied cross-shard SSI timestamps at the current
group-0 leader before allocating a commit timestamp or proposing writes
- migrate adapter read-modify-write snapshots to dedicated TSO
allocations while preserving single-shard and pre-Phase-D compatibility
- retain the parent branch's same-term allocation fences and
RawLatestCommitTS group_id/leader_fenced schema

## Safety
- activation commits cutover, Phase D, and the first post-Phase-D
allocation in order
- validation accepts only timestamps in the durable post-marker
allocation interval and fails closed on inactive state, stale
leadership, unsupported routing, or out-of-range values
- leader term is revalidated after floor/marker work and after the
allocation-floor commit
- proto output was regenerated from the combined schema

## Tests
- make gen
BREAKING_AGAINST='../.git#subdir=proto,branch=design/dedicated-tso-leader-routing'
- go test ./kv ./adapter . -run
'TSO|PhaseD|RenewHLC|CrossShard|ValidateTimestamp|RawLatestCommitTS'
-count=1
- go test ./kv -run
'TestRaftTSOAllocator(RejectsTermChange|DropsCommittedWindow|CommitsPhaseD)'
-count=1
- go test ./adapter -run
'^Test_consistency_satisfy_write_after_read_sequence$' -count=1
-timeout=10m
- go test ./adapter -run '^TestRedis_StreamXReadLatencyIsConstant$'
-count=1 -timeout=10m
- golangci-lint run ./... --timeout=5m --allow-parallel-runners

The full parallel go test ./... run completed all packages except two
long adapter stress tests after their test HLC ceilings expired under
concurrent local load; both tests passed when rerun independently.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca1a812729

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_raft.go
Comment on lines +337 to +341
if timestamp == 0 || timestamp > end {
return errors.Wrapf(ErrTSOTimestampInvalid,
"timestamp=%d phase_d_floor=%d allocation_floor=%d", timestamp, floor, end)
}
if timestamp <= floor {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unallocated Phase-D timestamp gaps

When Phase D is activated with no previous floor, or after a wall-clock/lease jump, the first reserved TSO Base can be far above PhaseDFloor (for example PhaseDFloor == 0 and Base == current_ms<<16). This validation accepts any timestamp in (floor, allocationFloor], so ValidateDurableTimestamp(1) can succeed even though group 0 never issued that timestamp, letting caller-supplied cross-shard StartTS values in the gap bypass the durable-TSO allocation gate. Track the first post-D base/ranges or set the marker floor to Base-1 before treating the interval as valid.

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.

Comment thread adapter/redis_txn.go Outdated
Comment on lines +2731 to +2736
func (r *RedisServer) beginTxnStartTS(ctx context.Context, label string) (uint64, error) {
readTimestamp, err := r.beginTxnReadTimestamp(ctx, label)
if err != nil {
return 0, err
}
return readTimestamp.Timestamp(), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Redis read vouchers for wide writes

When Phase D is active and the adapter read watermark predates the Phase-D floor, beginTxnReadTimestamp returns a voucher that authorizes that applied snapshot, but this helper discards it and returns only the numeric timestamp. Callers such as wide set/zset/list mutations then dispatch via dispatchElems/plain Dispatch; in a sharded catalog where the wide collection storage prefixes resolve to more than one group, the coordinator treats the StartTS as caller-supplied without the bound voucher and rejects the command as a pre-Phase-D timestamp. Return the ReadTimestamp through these paths or bind it with DispatchWithReadTimestamp at dispatch time.

Useful? React with 👍 / 👎.

Comment thread kv/tso.go
if err != nil {
return nil, err
}
defer revoke()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid revoking another in-flight read voucher

When the same ReadTimestamp is used by overlapping DispatchWithReadTimestamp calls during Phase D, each call increments the same (timestamp, ref) use count, but a call that successfully reaches consumeAppliedReadTimestampVoucher still runs this deferred revoke afterward. If one dispatch consumes its token while another prepared token is still in the map, the defer can delete/decrement the other dispatch's authorization before it validates, causing valid applied-watermark operations to fail as pre-Phase-D timestamps. Make the revoke conditional on the prepared token remaining unused, or mint a distinct ref per prepared dispatch.

Useful? React with 👍 / 👎.

Comment thread adapter/s3.go
err := s.retryS3Mutation(r.Context(), func() error {
readTS := s.readTS()
startTS, err := s.txnStartTS(r.Context(), readTS)
readTimestamp, err := s.beginTxnReadTimestamp(r.Context(), readTS, "s3 delete object: begin read timestamp")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind S3 delete-object read vouchers

When Phase D is active and the applied S3 read watermark predates the Phase-D floor, this call can return a ReadTimestamp carrying the voucher for the bucket-metadata read at readTS, but the later delete dispatch uses bare s.coordinator.Dispatch with only the object-manifest mutation. If the bucket metadata route and object route are split, the coordinator sees a single mutation shard and no read keys, so it accepts the legacy StartTS without consuming the voucher even though the operation semantically read another shard. Dispatch this transaction with readTimestamp.WithDispatchVoucher/DispatchWithReadTimestamp.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest review and merge-resolution findings:

  • bounded local TSO floor ReadIndex calls
  • rejected near-overflow TSO minimum windows before observing clocks
  • committed Phase-D activation at reservation base-1 and rechecked leader term before allocation floor commit
  • preserved applied-read vouchers through Redis/S3 transactional dispatch paths
  • kept startup timestamp allocation behind the startup rotation gate
  • resolved lint regressions from the merge update

Tests:

  • go test ./kv -count=1 -timeout=600s
  • go test ./adapter -run 'TestRedis.(List|Set|ZSet|ZAdd|ZRem|Hash|Expire|XTrim|PhaseD|Dedup)|Test.(List|Set|ZSet|Hash|Expire|XTrim).*|TestS3.*PhaseDBindsReadVoucher|TestS3CommitUploadPart|TestS3Server_AdminDeleteObject|TestDistribution.*Timestamp|Test.*Timestamp.*PhaseD|Test.*PhaseD|Test.*ReserveBatch|Test.*ValidateTimestamp' -count=1 -timeout=300s\n- go test . -run 'TestStartupGatedCoordinator|TestStartupRotation|Test.*TSO|Test.*Timestamp|Test.*EncryptionAdmin' -count=1 -timeout=300s\n- golangci-lint run ./kv ./adapter . --timeout=5m\n- commit hook: golangci-lint --config=.golangci.yaml run --fix\n- git diff --check\n- git verify-commit HEAD\n\nNote: earlier full go test ./adapter . -count=1 -timeout=600s reached the adapter package timeout after the root package completed; the targeted adapter suites above passed.\n\n@codex review

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (fea84f9):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: fea84f9b92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@bootjp
bootjp force-pushed the design/dedicated-tso-fsm branch from fea84f9 to 227aff5 Compare July 24, 2026 13:09
@bootjp

bootjp commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Review feedback addressed on the latest head.

Changes:

  • Keep Phase-D TSO reservations contiguous after the committed floor and reject min_timestamp gaps instead of blessing unallocated ranges.
  • Drain long headerless legacy KV FSM snapshots during TSO restore instead of parsing them as raw TSO state.
  • Document the new Phase-D contiguous allocation and legacy snapshot behavior.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -count=1 -timeout=600s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter -run 'TestDistributionServerGetTimestamp|TestS3(DeleteObject|AdminDeleteObject)PhaseDBindsReadVoucher|TestS3BeginTxnReadTimestampPhaseD|TestS3(CreateBucket|AdminCreateBucket|AdminPutObject)PhaseDBindsReadVoucher' -count=1 -timeout=300s\n- GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test . -run 'TestStartupGatedCoordinator|TestStartupRotation|Test.*TSO|Test.*Timestamp|Test.*EncryptionAdmin' -count=1 -timeout=300s\n- GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./kv ./adapter . --timeout=5m\n- git diff --check\n\n@codex review

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (227aff5):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if _, err := d.coordinator.Dispatch(ctx, req); err != nil {

P1 Badge Bind migration dispatches to the read voucher

This migration path receives a ReadTimestamp but dispatches the cross-key table-generation update without binding/registering its voucher. Under Phase D, tables created before the cutover commonly read at a pre-D applied watermark; when the metadata/generation keys are on different shards, the coordinator rejects this caller-supplied StartTS as pre-D instead of consuming the intended applied-read voucher, blocking the legacy key migration. Dispatch through kv.DispatchWithReadTimestamp(readTimestamp.WithDispatchVoucher(ctx), ...) for these migration writes.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/dynamodb_schema.go Outdated
return err
}
req.StartTS = readTS
if _, err := d.coordinator.Dispatch(ctx, req); err == nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind the Phase-D voucher on CreateTable dispatch

When Phase D is required and readTS is an applied pre-D watermark, beginTxnReadTimestamp returns a voucher that is only registered by kv.DispatchWithReadTimestamp; this direct Dispatch drops that capability. If the table metadata and generation keys route to different shards, ShardedCoordinator treats StartTS=readTS as an arbitrary caller timestamp and rejects it with ErrTSOTimestampPrePhaseD, so CreateTable can fail after Phase-D activation on upgraded clusters with pre-D data. Use the read timestamp's dispatch helper here, as the item-write path does.

Useful? React with 👍 / 👎.

Comment thread adapter/sqs_messages.go
Comment on lines +634 to +638
readTimestamp, err := s.beginTxnReadTimestamp(ctx, "sqs send message: begin read timestamp")
if err != nil {
return nil, 0, errors.WithStack(err)
}
readTS := readTimestamp.Timestamp()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve SQS send read vouchers through dispatch

Here beginTxnReadTimestamp may mint a Phase-D applied-read voucher, but the helper immediately collapses it to a bare uint64; the standard SendMessage paths then dispatch StartTS=readTS via plain coordinator.Dispatch. On Phase-D clusters with pre-D queue metadata, any standard send whose queue metadata/read keys and message index writes route across shards is rejected as an arbitrary pre-D timestamp instead of consuming the voucher, so standard SQS sends can fail after cutover. Return and bind the ReadTimestamp through the send dispatch path.

Useful? React with 👍 / 👎.

Comment thread adapter/sqs_catalog.go
if err != nil {
return false, errors.WithStack(err)
}
readTS := readTimestamp.Timestamp()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind SQS queue-generation mutations to vouchers

This path keeps the ReadTimestamp only long enough to extract readTS; the later CreateQueue transaction, and the analogous DeleteQueue/PurgeQueue generation bumps, still dispatch with plain s.coordinator.Dispatch. Under Phase D, when the queue meta/gen/tombstone prefixes route to different shards and the applied watermark is pre-D, cross-shard validation rejects that StartTS as an arbitrary caller timestamp, so queue create/delete/purge can fail after cutover. Carry the ReadTimestamp through those dispatches and bind it with DispatchWithReadTimestamp.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

Updated this PR against current main and addressed the current-head Phase-D voucher findings.

Changes:

  • bound SQS queue/message/tag/purge mutations to the Phase-D read voucher through dispatch
  • bound DynamoDB table creation/deletion and migration dispatch paths to the Phase-D read voucher
  • preserved the Redis route-read fence while carrying the reusable read voucher through transaction dispatch
  • added focused regression coverage for SQS and DynamoDB voucher binding

Validation:

  • go test ./adapter -run 'Test(SQS|Dynamo|Redis).*PhaseD|TestDispatch|TestTxn|TestRedis' -count=1 -timeout=300s
  • go test ./kv -run 'TestDispatchWithReadTimestamp|TestRaftTSOAllocator|TestTSOStateMachine|TestShardedCoordinatorDispatchTxn_PhaseD' -count=1 -timeout=300s
  • go test . -run 'TestStartupGatedCoordinator|TestStartupRotation|TestInternalTimestampOptions' -count=1 -timeout=300s
  • go test ./kv -count=1 -timeout=600s
  • go test . -count=1 -timeout=600s

Note: full go test ./adapter -count=1 -timeout=600s timed out while focused adapter coverage passed.

@codex review

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (c231fdb):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
adapter/s3_admin.go (1)

333-361: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

予約した applied-read voucher が dispatch へ届いていない S3 経路が 3 箇所あります。 共通の根本原因は、beginTxnReadTimestamp が返す kv.ReadTimestamp を破棄し、WithDispatchVoucher + kv.DispatchWithReadTimestamp へ渡していないことです。Phase-D では BeginReadTimestampThrough が voucher を予約するため、未消費の予約が残ります。kv/sharded_coordinator_txn_test.goTestDispatchWithReadTimestampVouchesEveryBoundDispatch は、未消費の voucher が残った状態で素の Dispatch を呼ぶと ErrTSOTimestampPrePhaseD になることを示しています。

  • adapter/s3_admin.go#L333-L361: Line 354 の s.coordinator.DispatchreadTimestamp.WithDispatchVoucher(ctx) + kv.DispatchWithReadTimestamp へ置き換えてください。
  • adapter/s3_admin.go#L444-L483: Line 474 の s.coordinator.Dispatch を同じ形へ置き換えてください。
  • adapter/s3_upload_part.go#L30-L39: Line 35 で得た kv.ReadTimestamps3UploadPartState へ保存し commitS3UploadPart まで引き回すか、allocateS3UploadPartVersion(Line 144)の 1 回の確保へ統合してください。

adapter/s3_admin.goadminCreateBucketTxn(Line 298-299)が正しい形です。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/s3_admin.go` around lines 333 - 361, 消費されずに残る applied-read voucher を各
S3 経路で dispatch に引き渡してください。adapter/s3_admin.go の 333-361(該当する ACL 更新処理)と 444-483
の Dispatch を、adminCreateBucketTxn の実装にならい、readTimestamp.WithDispatchVoucher(ctx)
と kv.DispatchWithReadTimestamp に置き換えてください。adapter/s3_upload_part.go の 30-39
では取得した kv.ReadTimestamp を s3UploadPartState に保存して commitS3UploadPart
まで引き回すか、allocateS3UploadPartVersion の単一の確保に統合し、予約した voucher が最終 dispatch
で消費されるようにしてください。
adapter/dynamodb_schema.go (1)

206-217: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

重大: dispatch のエラーがシャドーイングされ、失敗が成功として返ります。

行208 は if _, err := ...; err == nilerrif スコープ内に新規宣言します。この err は行209 の return nil にしか使われません。

行211 の isRetryableTransactWriteError(err) は、行208 の dispatch エラーではなく、外側スコープの err を読みます。外側の err は行186/191/198/202 の各チェックを通過した時点で必ず nil です。

結果として次の動作になります。

  • 行211 は常に isRetryableTransactWriteError(nil) を評価します。
  • nil が retryable と判定されない場合、行212 は errors.WithStack(nil) を返します。これは nil です。
  • 呼び出し元は、テーブルが作成されていないのに CreateTable が成功したと判断します。
  • nil が retryable と判定される場合、実際のエラー種別に関係なく必ず再試行し、非再試行エラーでも試行回数を使い切ります。

同じファイルの deleteTableWithRetry(行323-330)は if スコープ内でエラーを完結して処理しており、正しい形です。create 側も同じ形に揃えてください。

🐛 提案する修正
 		req.StartTS = readTS
 		dispatchCtx := readTimestamp.WithDispatchVoucher(ctx)
-		if _, err := kv.DispatchWithReadTimestamp(dispatchCtx, d.coordinator, req); err == nil {
-			return nil
-		}
-		if !isRetryableTransactWriteError(err) {
-			return errors.WithStack(err)
+		if _, dispatchErr := kv.DispatchWithReadTimestamp(dispatchCtx, d.coordinator, req); dispatchErr != nil {
+			if !isRetryableTransactWriteError(dispatchErr) {
+				return errors.WithStack(dispatchErr)
+			}
+		} else {
+			return nil
 		}
 		if err := waitRetryWithDeadline(ctx, deadline, backoff); err != nil {
 			return errors.WithStack(err)
 		}

adapter/phase_d_voucher_test.goTestDynamoDBCreateTablePhaseDBindsReadVoucher は成功パスのみを検証します。dispatch 失敗時にエラーが伝播することを確認する回帰テストの追加も推奨します。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/dynamodb_schema.go` around lines 206 - 217, 修正対象は CreateTable
のリトライ処理で、DispatchWithReadTimestamp のエラーが外側の err に正しく渡らず成功扱いされています。dispatch
のエラーを外側スコープで保持し、成功時だけ return nil、失敗時はその同じエラーを isRetryableTransactWriteError と
errors.WithStack に渡すよう、deleteTableWithRetry
と同じ形に揃えてください。TestDynamoDBCreateTablePhaseDBindsReadVoucher には dispatch
失敗が呼び出し元へ伝播する回帰テストを追加してください。
adapter/s3_upload_part.go (1)

30-39: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

prepareS3UploadPart が予約した voucher を破棄しています。

Line 35 は beginTxnReadTimestamp を呼びますが、Line 39 は Timestamp() だけを state.readTS へ保存し、kv.ReadTimestamp 自体を捨てます。Phase-D では BeginReadTimestampThrough が voucher を予約するため、この予約は消費されません。さらに allocateS3UploadPartVersion(Line 144)が同じアップロード処理でもう一度 beginTxnReadTimestamp を呼びます。1 リクエストで 2 つの read timestamp を確保する形になっています。

prepareS3UploadPart で確保した kv.ReadTimestamp を state に保存して commitS3UploadPart まで引き回すか、prepareS3UploadPart 側の確保を廃止して allocateS3UploadPartVersion の 1 回に統合してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/s3_upload_part.go` around lines 30 - 39, Update prepareS3UploadPart
and the surrounding upload flow so the read timestamp is acquired only once:
either retain the full kv.ReadTimestamp returned by beginTxnReadTimestamp in
s3UploadPartState and pass it through commitS3UploadPart, or remove that
acquisition from prepareS3UploadPart and rely on allocateS3UploadPartVersion.
Ensure the reserved voucher is consumed rather than discarded.
🧹 Nitpick comments (26)
kv/shard_store.go (1)

2339-2353: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

sort.Slice の代わりに slices.Sort を使えます。

slices は Line 10 で既に import 済みです。uint64 のスライスに対しては slices.Sort(ids) が簡潔で、比較関数の割り当てもありません。同じ指摘は Line 2633 の LocalStores にも当てはまります。

♻️ 変更案
-	sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
+	slices.Sort(ids)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/shard_store.go` around lines 2339 - 2353, Replace the sort.Slice call in
tsoCommitFloorGroupIDs with slices.Sort(ids), preserving ascending uint64
ordering; apply the same simplification to the LocalStores implementation around
line 2633.
adapter/redis_lists.go (1)

102-106: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

リトライ理由の文字列が 2 つの経路で同一です。

listPushCore(Line 102)と listPushCoreWithDedup(Line 398)はどちらも "redis list push: begin read timestamp" を渡します。この文字列が診断ログや監視に出る場合、dedup 有効/無効のどちらの経路かを区別できません。後者を "redis list push (dedup): begin read timestamp" のように分けると、障害解析が容易になります。

Also applies to: 398-402

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_lists.go` around lines 102 - 106, Update the retry-reason
string passed to beginTxnReadTimestamp in listPushCoreWithDedup to identify the
dedup path, using a distinct message such as “redis list push (dedup): begin
read timestamp”; leave the non-dedup listPushCore message unchanged.
kv/tso_fsm.go (2)

141-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

applyCutoverEntry の長さ検証は到達不能です。

Apply の分岐(Line 71)は bytes.Equal(data, []byte(tsoCutoverEnvelope)) で既に完全一致を確認します。したがって Line 142 の再検証は常に偽になり、エラーパスは死にコードです。防御的に残す方針であれば問題ありませんが、applyPhaseDEntry と対称にするなら Apply 側を bytes.HasPrefix にして長さ検証を applyCutoverEntry に委ねる方が一貫します。

♻️ 一貫性のための変更案
-	case bytes.Equal(data, []byte(tsoCutoverEnvelope)):
+	case bytes.HasPrefix(data, []byte(tsoCutoverEnvelope)):
 		return f.applyCutoverEntry(data)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_fsm.go` around lines 141 - 150, Update the cutover dispatch in Apply
to use bytes.HasPrefix instead of requiring bytes.Equal, allowing
applyCutoverEntry to perform the complete envelope-length validation. Keep
applyCutoverEntry’s exact validation and error handling unchanged, matching the
applyPhaseDEntry flow.

458-467: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

IsVolatileOnlyPayload の条件式は演算子優先度に依存しており読みにくいです。

Go では &&|| より強く束縛するため、現在の式は意図どおり「3 つの (長さ AND プレフィックス) 条件の OR」として評価されます。動作は正しいですが、括弧または early return を使うと将来の誤読を防げます。

♻️ 可読性の改善案
 func (f *TSOStateMachine) IsVolatileOnlyPayload(payload []byte) bool {
 	if bytes.Equal(payload, []byte(tsoCutoverEnvelope)) {
 		return true
 	}
-	return len(payload) == hlcLeaseEntryLen && payload[0] == raftEncodeHLCLease ||
-		len(payload) == len(tsoAllocationFloorEnvelope)+hlcLeasePayloadLen &&
-			bytes.HasPrefix(payload, []byte(tsoAllocationFloorEnvelope)) ||
-		len(payload) == len(tsoPhaseDEnvelope)+hlcLeasePayloadLen &&
-			bytes.HasPrefix(payload, []byte(tsoPhaseDEnvelope))
+	if len(payload) == hlcLeaseEntryLen && payload[0] == raftEncodeHLCLease {
+		return true
+	}
+	if len(payload) == len(tsoAllocationFloorEnvelope)+hlcLeasePayloadLen &&
+		bytes.HasPrefix(payload, []byte(tsoAllocationFloorEnvelope)) {
+		return true
+	}
+	return len(payload) == len(tsoPhaseDEnvelope)+hlcLeasePayloadLen &&
+		bytes.HasPrefix(payload, []byte(tsoPhaseDEnvelope))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_fsm.go` around lines 458 - 467, Improve readability in
TSOStateMachine.IsVolatileOnlyPayload by making the three OR-separated payload
checks explicit with parentheses or early returns, rather than relying on &&/||
precedence. Preserve the existing matching behavior for HLC lease,
allocation-floor, and phase-D payloads, as well as the tsoCutoverEnvelope check.
adapter/redis_compat_helpers.go (1)

785-788: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

normalizeStartTS を再利用してください。

Line 786-788 は ^uint64(0) センチネルを 0 に正規化するロジックを手書きしています。同じ処理を行う normalizeStartTS ヘルパーが既に存在し、rewriteListTxn(Line 1313)や redis_lists.go の各所で使われています。重複を避けると、センチネル判定の変更が 1 箇所で済みます。

♻️ 変更案
-	startTS := readTimestamp.Timestamp()
-	if startTS == ^uint64(0) {
-		startTS = 0
-	}
+	startTS := normalizeStartTS(readTimestamp.Timestamp())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_compat_helpers.go` around lines 785 - 788, Replace the inline
^uint64(0)-to-zero normalization after readTimestamp.Timestamp() with the
existing normalizeStartTS helper, preserving the resulting startTS behavior and
reusing the shared sentinel handling.
adapter/redis_zset_cmds.go (1)

1319-1321: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

r.blockWaitFallback がゼロのとき 2 か所で扱いが異なります。

waitForBlockedCommandUpdate(Line 1400-1403)はゼロ以下の値を defaultRedisBlockWaitFallback に置換します。一方 Line 1350 の fast = signaled && time.Since(lastFullCheck) < r.blockWaitFallback は生の値を使うため、ゼロだと常に false になり fast モードが無効化されます。

動作は安全側(常に full check)に倒れるため、現状のバグではありません。ただし将来の読み手が混乱します。解決済みの fallback 値を 1 か所で算出し、両方で使う方が明確です。

Also applies to: 1349-1350, 1399-1403

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_zset_cmds.go` around lines 1319 - 1321, Normalize
r.blockWaitFallback once to defaultRedisBlockWaitFallback when it is zero or
negative, then reuse that resolved duration in both the fast-mode check and
waitForBlockedCommandUpdate. Ensure both paths apply identical fallback
semantics without changing the existing safety behavior.
adapter/redis_delta_compactor.go (1)

613-628: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

dispatchCompaction は voucher を context 経由で暗黙に受け取ります。

シグネチャは readTS uint64 だけを受け取り、voucher は ctx に埋め込まれた状態を前提とします。呼び出し元が WithDispatchVoucher を忘れると、コンパイルは通り、voucher なしで dispatch されます。kv.ReadTimestamp を明示的な引数にすると、この失敗を型で防げます。ファイル内の他の Redis アダプター(例: dispatchAndSignalZSet)は既にこの形式を採用しています。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_delta_compactor.go` around lines 613 - 628, Update
DeltaCompactor.dispatchCompaction to accept an explicit kv.ReadTimestamp
argument instead of relying on a voucher embedded in context, and pass that
value through the timestamp allocation and operation-group dispatch. Update all
callers, including the compaction dispatch path, to provide the ReadTimestamp
explicitly while preserving existing normalization behavior.
distribution/catalog_test.go (1)

368-373: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

snapshot.Routes[0] の前に長さを検証してください。

SnapshotAt が空のルート集合を返した場合、Line 372 は index out of range で panic します。テストは失敗しますが、原因がスタックトレースからしか読み取れません。require.Len を先に置くと失敗メッセージが明確になります。

💚 変更案
 	require.Equal(t, first.Version, snapshot.Version)
 	require.Equal(t, firstTS, snapshot.ReadTS)
+	require.Len(t, snapshot.Routes, 1)
 	require.Equal(t, uint64(1), snapshot.Routes[0].RouteID)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@distribution/catalog_test.go` around lines 368 - 373, Validate the route
collection length before indexing it in the SnapshotAt test. Add a require.Len
assertion for snapshot.Routes before the snapshot.Routes[0].RouteID check,
preserving the existing assertions and expected route count.
kv/tso_raft.go (3)

940-975: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

シャドウ再試行ループの上限を検討してください。

nextAfter はレガシー候補が TSO フロア以下の間、無制限に再試行します。ctx のチェックはありますが、ctx に期限がない場合、フロアが常に先行する状況ではループが長時間続きます。ログも 1 反復ごとに出ます。反復回数の上限、またはバックオフの追加を検討してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_raft.go` around lines 940 - 975, Add a bounded retry policy or backoff
to the retry loop in ShadowTimestampAllocator.nextAfter when legacyTS does not
exceed reservation.PreviousAllocationFloor, while preserving context
cancellation handling and successful return paths. Avoid unbounded per-iteration
warning logs by limiting or throttling the corresponding discard logging.

665-679: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

uint32(n) の二重変換をまとめてください。

Line 666 と Line 674 は同じ uint32(n) を別々に計算します。nvalidateTSOMinimumWindowmaxHLCBatchSize 以下に検証済みなので、静的解析の桁あふれ警告は誤検知です。可読性のため、変換を 1 回にまとめてください。

♻️ 提案する変更
+	count := uint32(n) //nolint:gosec // n is bounded by maxHLCBatchSize.
 	resp, err := pb.NewDistributionClient(conn).GetTimestamp(ctx, &pb.GetTimestampRequest{
-		Count:           uint32(n), //nolint:gosec // n is bounded by maxHLCBatchSize.
+		Count:           count,
 		MinTimestamp:    min,
 		ActivateCutover: activate,
 		ActivatePhaseD:  activatePhaseD,
 	})
 	if err != nil {
 		return empty, errors.Wrap(err, "tso request leader batch")
 	}
-	count := uint32(n) //nolint:gosec // n is validated before this request.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_raft.go` around lines 665 - 679, In the GetTimestamp request flow,
compute the validated n-to-uint32 conversion once and reuse that variable for
both the request Count field and the response count comparison. Keep the
existing overflow-suppression justification on the single conversion and
preserve the protocol validation behavior.

Source: Linters/SAST tools


44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

インターフェースの引数に名前を付けてください。

ReserveBatchAfter(context.Context, int, uint64, bool, bool) は bool が 2 つ連続します。呼び出し側は activateCutoveractivatePhaseD の順序を取り違えやすいです。順序を誤ると Phase D が意図せず起動します。引数名を宣言に付けると誤用を減らせます。

♻️ 提案する変更
 type TSOReservationAllocator interface {
-	ReserveBatchAfter(context.Context, int, uint64, bool, bool) (TSOReservation, error)
+	ReserveBatchAfter(
+		ctx context.Context,
+		n int,
+		min uint64,
+		activateCutover bool,
+		activatePhaseD bool,
+	) (TSOReservation, error)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_raft.go` around lines 44 - 46, TSOReservationAllocator の
ReserveBatchAfter 宣言に全引数の名前を追加し、特に2つの bool を activateCutover、activatePhaseD
の順序で明示してください。既存の型と戻り値は変更せず、呼び出し側が引数の意図を確認できる宣言に更新してください。
kv/tso_test.go (1)

333-380: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

BeginReadTimestampThrough の 4 つのテストをテーブル駆動にまとめてください。

4 つのテストは同じ構造です。差分は phaseDActivephaseDRequiredvalidateErr と期待値だけです。テーブル駆動にすると重複が減ります。また、このファイルの他のテストは t.Parallel() を使いますが、Line 333-407 のテストは使っていません。整合させてください。

コーディングガイドラインに従っています: "Unit tests must be co-located with packages in *_test.go files; prefer table-driven test cases"。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_test.go` around lines 333 - 380,
4つのBeginReadTimestampThroughテストを1つのテーブル駆動テストに統合し、各ケースでphaseDActive、phaseDRequired、validateErrと期待する戻り値・呼び出し回数・検証値を定義してください。各サブテストでは既存の検証内容を維持し、t.Parallel()を追加してファイル内のテスト慣行に合わせてください。

Source: Coding guidelines

adapter/redis_txn.go (1)

471-495: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

cancelOnce は不要です。エラーの選択も非決定的です。

2つの点があります。

  1. context.CancelFunc は複数回呼び出しても安全です。sync.Once による保護は不要です。cancel() を直接呼べます。
  2. 失敗したゴルーチンが cancel() を呼ぶと、残りのゴルーチンは context.CancelederrCh に送ります。for err := range errCh は最初に読み取った1件を返します。チャネルの受信順は非決定的なため、本来の原因エラーではなく context.Canceled が返る場合があります。障害調査が難しくなります。

errgroup.WithContext を使うと、最初に発生したエラーだけが保持され、この両方が解消されます。

♻️ errgroup を使う修正案
-	leaseCtx, cancel := context.WithCancel(ctx)
-	defer cancel()
-
-	errCh := make(chan error, len(groupKeys))
-	var wg sync.WaitGroup
-	var cancelOnce sync.Once
+	eg, leaseCtx := errgroup.WithContext(ctx)
 	for _, key := range groupKeys {
-		wg.Add(1)
-		go func(k []byte) {
-			defer wg.Done()
+		k := key
+		eg.Go(func() error {
 			if _, err := kv.LeaseReadForKeyThrough(r.coordinator, leaseCtx, k); err != nil {
-				errCh <- errors.WithStack(err)
-				cancelOnce.Do(cancel)
+				return errors.WithStack(err)
 			}
-		}(key)
+			return nil
+		})
 	}
-	wg.Wait()
-	close(errCh)
-	for err := range errCh {
-		if err != nil {
-			return err
-		}
-	}
-	return nil
+	return errors.WithStack(eg.Wait())
 }

golang.org/x/sync/errgroup の import が必要です。sync の import が他で使われていない場合は削除してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_txn.go` around lines 471 - 495, Replace the manual WaitGroup,
error channel, and cancelOnce logic in the transaction lease-read flow with
errgroup.WithContext, using the derived group context for LeaseReadForKeyThrough
and returning the group’s first error. Remove now-unused synchronization imports
and preserve cancellation of remaining reads after the first failure.
adapter/dynamodb_item_write.go (1)

449-456: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

WithDispatchVoucher の呼び出しが二重になっています。

commitItemWritereadTimestamp.WithDispatchVoucher(ctx) で voucher を context に載せ、その context を kv.DispatchWithReadTimestamp に渡します。kv.DispatchWithReadTimestamp は内部で context から ReadTimestamp を取り出し、prepare した後に再度 WithDispatchVoucher を呼びます。

動作は正しいですが、呼び出し側が context への詰め込みを知っている必要があります。kv.DispatchWithReadTimestampReadTimestamp を引数で受け取る形にすると、この結合がなくなります。この変更は kv/tso.go と全アダプター呼び出し側に及ぶため、別 PR での対応でも問題ありません。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/dynamodb_item_write.go` around lines 449 - 456, commitItemWrite 内の
readTimestamp.WithDispatchVoucher 呼び出しを削除し、kv.DispatchWithReadTimestamp が
ReadTimestamp を引数として受け取る API に変更して、内部で必要な context 処理を一元化してください。kv/tso.go
の実装と全アダプター呼び出し側を更新し、context への voucher 設定を呼び出し側に要求しない形を維持してください。
main.go (1)

2122-2147: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

floorProviders ...kv.TSOCutoverFloorProvider を単一引数にしてください。

configureCoordinatorTSO は可変長引数を受け取りますが、L2143-2145 で先頭の1要素だけを使い、残りを黙って破棄します。呼び出し側(L530)も1つだけ渡します。可変長にすると、2つ目以降を渡した呼び出しがコンパイルエラーにならず、実行時に無視されます。

floorProvider kv.TSOCutoverFloorProvider の単一引数に変更すると、この曖昧さがなくなります。

♻️ 修正案
 func configureCoordinatorTSO(
 	coordinate *kv.ShardedCoordinator,
 	shardGroups map[uint64]*kv.ShardGroup,
-	floorProviders ...kv.TSOCutoverFloorProvider,
+	floorProvider kv.TSOCutoverFloorProvider,
 ) (coordinatorTSOWiring, error) {
@@
 	tsoGroup, dedicated := shardGroups[dedicatedTSORaftGroupID]
 	if !dedicated {
 		return configureLegacyCoordinatorTSO(coordinate)
 	}
-	var floorProvider kv.TSOCutoverFloorProvider
-	if len(floorProviders) > 0 {
-		floorProvider = floorProviders[0]
-	}
 	return configureDedicatedCoordinatorTSO(coordinate, tsoGroup, floorProvider)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.go` around lines 2122 - 2147, Change configureCoordinatorTSO to accept a
single kv.TSOCutoverFloorProvider parameter instead of the variadic
floorProviders argument, and pass that value directly to
configureDedicatedCoordinatorTSO. Update all callers, including the existing
call site, to provide the single floor provider explicitly.
kv/tso_raft_test.go (1)

100-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

//nolint:gosec の代わりに変換ヘルパーを使えます。

このファイルには HLC タイムスタンプ生成のための //nolint:gosec が4箇所あります。同ファイル内では positiveIntToUint64 のような変換ヘルパーが既に使われています。ミリ秒値を uint64 へ変換する小さなテストヘルパー(例: testHLCFromWallMillis(t time.Time) uint64)を1つ用意すると、4箇所の //nolint を削除できます。

コーディングガイドラインは //nolint の追加を避け、リファクタを優先することを求めています。

Also applies to: 287-287, 301-301, 372-372

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_raft_test.go` at line 100, Replace the four HLC millisecond
conversions currently suppressed with //nolint:gosec by adding one file-local
helper such as testHLCFromWallMillis(t time.Time) uint64. Have the helper
perform the Unix-millisecond-to-uint64 conversion, update all four call sites to
use it, and remove the corresponding nolint directives while preserving the
existing HLC timestamp behavior.

Source: Coding guidelines

adapter/redis_hash_cmds.go (1)

567-598: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

*ReadTimestamp 版の新設時に、旧関数の本体をそのまま複製しています。 どちらの新関数もディスパッチ呼び出し以外は旧関数と同一です。要素生成ロジックが二重化するため、TTL 形式やメタ形式の変更が片方だけに入るリスクがあります。要素生成を共通ヘルパーへ抽出し、各関数はディスパッチのみを担当させてください。

  • adapter/redis_hash_cmds.go#L567-L598: persistHashTxn(L532-565)と共通の要素生成ヘルパー(例: persistHashElems)を抽出し、両関数から呼び出してください。
  • adapter/redis_collection_ttl.go#L151-L166: dispatchCollectionExpire(L135-149)と共通化してください。旧関数の呼び出し元が残っていない場合は旧関数を削除してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_hash_cmds.go` around lines 567 - 598,
重複した要素生成ロジックを共通ヘルパーへ抽出し、adapter/redis_hash_cmds.go:567-598 の
persistHashReadTimestampTxn と persistHashTxn
の両方から利用して、各関数はディスパッチに専念させてください。adapter/redis_collection_ttl.go:151-166 の処理も
dispatchCollectionExpire と要素生成を共通化し、旧関数の呼び出し元が残っていなければ削除してください。
kv/sharded_coordinator_txn_test.go (2)

386-388: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

t.Parallel() の欠落を確認してください。

このファイルで追加した他のテストはすべて t.Parallel() を呼びます。TestReadTimestampVoucherBindingShadowsParentCapability だけ呼びません。alloccoord はテストローカルで生成するため、並列実行しても他テストと状態を共有しません。意図的な除外でなければ、一貫性のために追加してください。

♻️ 提案する修正
 func TestReadTimestampVoucherBindingShadowsParentCapability(t *testing.T) {
+	t.Parallel()
 	prePhaseDErr := errors.Join(ErrTSOTimestampInvalid, ErrTSOTimestampPrePhaseD)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/sharded_coordinator_txn_test.go` around lines 386 - 388,
「TestReadTimestampVoucherBindingShadowsParentCapability」に t.Parallel()
を追加し、同ファイル内の他テストと並列実行の扱いを統一してください。

484-485: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

require.Nil ではなく require.NoError を使ってください。

state.Applyerror を返します。require.NoError はエラー内容をメッセージに出力するため、失敗時の診断が容易になります。testifylint を有効にしている場合は require-error 系の指摘対象にもなります。

♻️ 提案する修正
-	require.Nil(t, state.Apply(marshalTSOCutover()))
-	require.Nil(t, state.Apply(marshalTSOPhaseD(0)))
+	require.NoError(t, state.Apply(marshalTSOCutover()))
+	require.NoError(t, state.Apply(marshalTSOPhaseD(0)))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/sharded_coordinator_txn_test.go` around lines 484 - 485, Replace the
require.Nil assertions around state.Apply in the affected test with
require.NoError, preserving both Apply calls and their arguments so failures
report the returned error details.
adapter/redis_expire_cmds.go (1)

95-99: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

ストレージエラーと「キー無し」を区別してください。

readRedisStringAt が返すすべてのエラーを「キーが存在しない」として扱います。この結果、Pebble の I/O エラーやデコード失敗でも GETDEL は削除を行わず、クライアントには nil を返します。呼び出し元はエラーを検出できません。store.ErrKeyNotFound だけを「キー無し」に分類し、それ以外は伝播させてください。

🛡️ 提案する修正
 	raw, _, err := r.readRedisStringAt(key, readTS)
 	if err != nil {
-		// Key may have expired or been deleted between type check and read.
-		return nil, false, nil //nolint:nilerr // treat not-found/expired as nil value
+		// Key may have expired or been deleted between type check and read.
+		if cockerrors.Is(err, store.ErrKeyNotFound) {
+			return nil, false, nil
+		}
+		return nil, false, cockerrors.WithStack(err)
 	}
 	return raw, true, nil

コーディングガイドラインは //nolint の追加を避け、リファクタリングを優先することを求めています。この変更は nolint も同時に除去します。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 95 - 99, readRedisStringAt
のエラー処理を更新し、store.ErrKeyNotFound の場合だけキー無しとして nil
値を返し、それ以外のストレージエラーやデコードエラーは呼び出し元へ伝播させてください。GETDEL
の削除処理と戻り値が非NotFoundエラーを隠さないよう維持し、該当ブロックの //nolint:nilerr も削除してください。

Source: Coding guidelines

adapter/redis_stream_cmds.go (1)

1334-1346: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

r.readTS() をループの外へ移動してください。

xreadCheckTypes は各キーごとに r.readTS() を呼びます。型チェックは 1 つのスナップショットで行うほうが一貫します。またキー数に比例した呼び出しを避けられます。

♻️ 修正案
 func (r *RedisServer) xreadCheckTypes(ctx context.Context, req xreadRequest) error {
+	readTS := r.readTS()
 	for _, key := range req.keys {
-		readTS := r.readTS()
 		typ, err := r.keyTypeAtExpect(ctx, key, readTS, redisTypeStream)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_stream_cmds.go` around lines 1334 - 1346, Move the single
r.readTS() call in xreadCheckTypes outside the key loop, store its result, and
reuse that snapshot for every keyTypeAtExpect invocation while preserving the
existing error and wrong-type handling.
adapter/s3_hlc_fence_test.go (2)

259-259: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

context.Context を第 1 引数へ移動してください。

Go の慣習では context.Context が第 1 引数です。テストヘルパーでは t *testing.T の後ろに置くと revivecontext-as-argument に抵触する場合があります。seedS3ObjectForReadVoucherTest(ctx, t, st, bucket, key) の順序、または ctx を引数から外して内部で t.Context() を使う形を検討してください。

コーディングガイドラインに従い、.golangci.yaml のリンター設定に適合させてください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/s3_hlc_fence_test.go` at line 259, Update
seedS3ObjectForReadVoucherTest so context.Context is the first parameter, using
the signature and all call sites in the order
seedS3ObjectForReadVoucherTest(ctx, t, st, bucket, key). Ensure the helper and
its callers comply with the context-as-argument lint rule.

Source: Coding guidelines


221-222: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

この 2 つのテストに t.Parallel() がありません。

同じファイル内の他のテストはすべて t.Parallel() を呼びます。TestS3CommitUploadPartRechecksUploadAtLatestAppliedWatermarkTestS3CommitUploadPartIncludesUploadMetaInReadSet は共有状態を持たないため、並列化できます。

♻️ 修正案
 func TestS3CommitUploadPartRechecksUploadAtLatestAppliedWatermark(t *testing.T) {
+	t.Parallel()
+
 	st := store.NewMVCCStore()

Also applies to: 240-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/s3_hlc_fence_test.go` around lines 221 - 222,
対象の2テスト、TestS3CommitUploadPartRechecksUploadAtLatestAppliedWatermark と
TestS3CommitUploadPartIncludesUploadMetaInReadSet の先頭で t.Parallel()
を呼び出し、同一ファイル内の他テストと同様に並列実行できるようにする。
main_tso_routing_test.go (1)

248-259: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

エラー生成を cockroachdb/errors に揃えることを検討してください。

行255 は fmt.Errorf を使います。同等のモックである kv/tso_raft_test.gorecordingTSOEngineerrors.Newf を使います。リポジトリのガイドラインはエラーを github.com/cockroachdb/errors で扱うことを求めます。テストコードでも表記を揃えると、スタックトレースの扱いが一貫します。

♻️ 提案する変更
-			return nil, fmt.Errorf("unexpected TSO apply result %T", result)
+			return nil, errors.Newf("unexpected TSO apply result %T", result)

インポートを差し替えてください。

-	"fmt"
 	"sync/atomic"
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/errors"
 	"github.com/bootjp/elastickv/distribution"

コーディングガイドラインの「Wrap errors with github.com/cockroachdb/errors at boundaries in Go code」に基づく指摘です。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main_tso_routing_test.go` around lines 248 - 259, Update
mainTSOEngine.Propose to use github.com/cockroachdb/errors for the unexpected
TSO apply result instead of fmt.Errorf, and adjust imports accordingly. Preserve
the existing error message and type formatting.

Source: Coding guidelines

adapter/grpc.go (1)

146-149: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

GroupCommittedTimestampFloor のエラーを一律 FailedPrecondition に変換しています。

GroupCommittedTimestampFloor は複数の原因でエラーを返します。kv/tso_floor_test.go はローカルリーダーシップ不足時の ErrTSOCommitFloorUnavailable と、境界付き ReadIndex を確認しています。したがって context のキャンセルやデッドライン超過も到達しえます。

現状はすべて codes.FailedPrecondition になります。クライアントはリーダー未確定(再試行して別ノードへ)と、呼び出しタイムアウト(バックオフして同一ノードへ)を区別できません。kv.NewLeaderRoutedTSOAllocator のフェンス処理はこの応答でリーダー到達可否を判断するため、判定精度が下がります。

原因に応じたコードへ分岐することを検討してください。

♻️ 提案する変更
 		ts, err := reader.GroupCommittedTimestampFloor(ctx, groupID)
 		if err != nil {
+			switch {
+			case errors.Is(err, context.Canceled):
+				return nil, errors.WithStack(status.Error(codes.Canceled, err.Error()))
+			case errors.Is(err, context.DeadlineExceeded):
+				return nil, errors.WithStack(status.Error(codes.DeadlineExceeded, err.Error()))
+			}
 			return nil, errors.WithStack(status.Error(codes.FailedPrecondition, err.Error()))
 		}

また、行148 は err.Error() をそのままクライアントへ返します。内部エラー文字列の露出範囲が要件に合うかを確認してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/grpc.go` around lines 146 - 149, Update the error handling around
GroupCommittedTimestampFloor to map context cancellation and deadline errors to
the corresponding gRPC status codes, while preserving FailedPrecondition for
ErrTSOCommitFloorUnavailable and other leader-readiness failures. Keep the
existing wrapped error behavior as appropriate, but avoid exposing raw internal
err.Error() text to clients unless the API contract requires it.
adapter/distribution_server_test.go (1)

263-274: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

このテストは「legacy server の拒否」を証明できていない可能性があります。

TestDistributionServerGetTimestamp_LeaderRoutedRejectsLegacyServer は 75ms のデッドラインを設定し、context.DeadlineExceeded を期待します。

デッドライン超過は複数の原因で発生します。

  • サーバーが FailedPrecondition を返し、LeaderRoutedTSOAllocator が再試行を続けてデッドラインに達した(意図した挙動)。
  • 負荷の高い CI 環境で、gRPC の初回ダイヤルまたは1回目の RPC 自体が 75ms を超えた(意図しない経路)。

どちらの場合も同じ結果になるため、テストは fail-closed 動作を区別できません。

サーバー側のリクエスト受信回数を記録するか、serverAlloc を持たないサーバーが返すエラー種別を直接検証する形に変更することを検討してください。デッドラインに依存しない検証であれば t.Parallel() も追加できます。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/distribution_server_test.go` around lines 263 - 274, Update
TestDistributionServerGetTimestamp_LeaderRoutedRejectsLegacyServer to verify the
legacy server rejection directly instead of inferring it from a 75ms context
deadline. Assert the server-side error/status or track and validate the request
count so the test distinguishes FailedPrecondition rejection from dial or RPC
timing; then remove the deadline-dependent assertion and add t.Parallel() if the
revised test is independent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapter/distribution_server.go`:
- Around line 244-252: Update the ValidateTimestamp response construction around
ValidateDurableTimestamp to derive PhaseDActive from the allocator state instead
of always setting it to true. Extend the optional timestamp allocator interface
with PhaseDActive() bool, assign the returned value to resp.PhaseDActive
alongside PhaseDFloor() and AllocationFloor(), and preserve the existing
fallback behavior when the allocator does not implement the interface.

In `@adapter/redis_delta_compactor.go`:
- Around line 316-324: 各 dispatch ごとに ReadTimestamp voucher
を再取得するよう、adapter/redis_delta_compactor.go:316-324 の SyncOnce と
compactBackgroundHandlers の処理を更新してください。dispatch 直前に BeginReadTimestampThrough
を呼び、各呼び出しから voucher 付き context を生成して、同じ tickCtx や券を複数 dispatch で再利用しないでください。関連する
adapter/redis_lists.go:221-222 および adapter/redis_compat_helpers.go:781-784 でも同じ
dispatch 単位の取得・context 生成に統一してください。

In `@adapter/s3.go`:
- Around line 2514-2530: Replace direct BeginReadTimestampThrough-based
dispatches with kv.DispatchWithReadTimestamp using the read timestamp’s
WithDispatchVoucher(ctx). Update AdminPutBucketAcl and the admin delete/other
dispatches in adapter/s3_admin.go (lines 354, 474, and 518), cleanup/delete
dispatches in adapter/s3.go (lines 1530, 1601, and 1850), and multipart admin
dispatch in adapter/s3_admin_objects.go (line 266); adapter/s3.go lines
2514-2530 requires the corresponding read-timestamp voucher flow, and
adapter/sqs_messages.go lines 1002-1007 requires the same replacement. Preserve
each operation’s existing request and error handling.

In `@docs/design/2026_04_16_partial_centralized_tso.md`:
- Around line 3-7: Resolve the lifecycle marker inconsistency for the design
document: either rename it to the implemented marker when M1–M7 are the complete
milestone set, updating every reference found by searching for the old filename,
or define the remaining runtime reload and production latency/alerting work as
M8 in the milestone table so the partial marker remains accurate. Keep the
status text, Remaining section, and milestone table consistent with the chosen
policy.
- Around line 62-63: Increase the leading indentation of the continuation line
beginning “malformed control entries” to four spaces so it remains part of
numbered item 10, matching the indentation of the other continuation lines.

In `@kv/keyviz_label.go`:
- Around line 124-130: Add an exported capability check on
keyVizLabeledCoordinator, such as supportsAppliedReadVoucher, that reports
whether inner implements AppliedReadTimestampVoucher rather than relying on the
wrapper’s method set. Update validateAppliedReadTimestamp in tso.go to use this
check and reject unsupported coordinators before BeginReadTimestampThrough
returns a voucher-bearing ReadTimestamp, while preserving the existing dispatch
behavior.

In `@kv/shard_store.go`:
- Around line 2419-2430: Update the fallback flow around
GroupCommittedTimestampFloor and leaderAddrFromEngine so that, after a local
leader fence failure, it detects when the resolved leader address belongs to the
current node and immediately returns the original fence error instead of
forwarding an RPC to itself. Preserve the existing remote-leader forwarding
behavior for addresses belonging to other nodes.

In `@kv/tso_fsm.go`:
- Around line 433-439: Update hasHeaderlessLegacyKVFSMSnapshotPayload to
identify headerless legacy snapshots using the recognized magic headers
EKVMVCC2, EKVPBBL1, or EKVSSTI1, matching restoreLegacyKVFSMSnapshot, rather
than relying only on len(peeked) > tsoSnapshotV4Len. Preserve the existing peek
error handling and return false for oversized payloads without a recognized
legacy header.

In `@kv/tso.go`:
- Around line 179-199: Define and enforce a clear voucher-use contract: in
kv/tso.go lines 179-199, either implement the prepared-count limit using
ErrTSOReadVoucherLimit or remove prepared and the unused error and document
unlimited ReadTimestamp reuse; if enforcing a limit, update
adapter/sqs_reaper.go lines 76-93 so each deletion dispatch obtains a
ReadTimestamp without exceeding that limit, such as acquiring one per record.

---

Outside diff comments:
In `@adapter/dynamodb_schema.go`:
- Around line 206-217: 修正対象は CreateTable のリトライ処理で、DispatchWithReadTimestamp
のエラーが外側の err に正しく渡らず成功扱いされています。dispatch のエラーを外側スコープで保持し、成功時だけ return
nil、失敗時はその同じエラーを isRetryableTransactWriteError と errors.WithStack
に渡すよう、deleteTableWithRetry
と同じ形に揃えてください。TestDynamoDBCreateTablePhaseDBindsReadVoucher には dispatch
失敗が呼び出し元へ伝播する回帰テストを追加してください。

In `@adapter/s3_admin.go`:
- Around line 333-361: 消費されずに残る applied-read voucher を各 S3 経路で dispatch
に引き渡してください。adapter/s3_admin.go の 333-361(該当する ACL 更新処理)と 444-483 の Dispatch
を、adminCreateBucketTxn の実装にならい、readTimestamp.WithDispatchVoucher(ctx) と
kv.DispatchWithReadTimestamp に置き換えてください。adapter/s3_upload_part.go の 30-39 では取得した
kv.ReadTimestamp を s3UploadPartState に保存して commitS3UploadPart
まで引き回すか、allocateS3UploadPartVersion の単一の確保に統合し、予約した voucher が最終 dispatch
で消費されるようにしてください。

In `@adapter/s3_upload_part.go`:
- Around line 30-39: Update prepareS3UploadPart and the surrounding upload flow
so the read timestamp is acquired only once: either retain the full
kv.ReadTimestamp returned by beginTxnReadTimestamp in s3UploadPartState and pass
it through commitS3UploadPart, or remove that acquisition from
prepareS3UploadPart and rely on allocateS3UploadPartVersion. Ensure the reserved
voucher is consumed rather than discarded.

---

Nitpick comments:
In `@adapter/distribution_server_test.go`:
- Around line 263-274: Update
TestDistributionServerGetTimestamp_LeaderRoutedRejectsLegacyServer to verify the
legacy server rejection directly instead of inferring it from a 75ms context
deadline. Assert the server-side error/status or track and validate the request
count so the test distinguishes FailedPrecondition rejection from dial or RPC
timing; then remove the deadline-dependent assertion and add t.Parallel() if the
revised test is independent.

In `@adapter/dynamodb_item_write.go`:
- Around line 449-456: commitItemWrite 内の readTimestamp.WithDispatchVoucher
呼び出しを削除し、kv.DispatchWithReadTimestamp が ReadTimestamp を引数として受け取る API
に変更して、内部で必要な context 処理を一元化してください。kv/tso.go の実装と全アダプター呼び出し側を更新し、context への
voucher 設定を呼び出し側に要求しない形を維持してください。

In `@adapter/grpc.go`:
- Around line 146-149: Update the error handling around
GroupCommittedTimestampFloor to map context cancellation and deadline errors to
the corresponding gRPC status codes, while preserving FailedPrecondition for
ErrTSOCommitFloorUnavailable and other leader-readiness failures. Keep the
existing wrapped error behavior as appropriate, but avoid exposing raw internal
err.Error() text to clients unless the API contract requires it.

In `@adapter/redis_compat_helpers.go`:
- Around line 785-788: Replace the inline ^uint64(0)-to-zero normalization after
readTimestamp.Timestamp() with the existing normalizeStartTS helper, preserving
the resulting startTS behavior and reusing the shared sentinel handling.

In `@adapter/redis_delta_compactor.go`:
- Around line 613-628: Update DeltaCompactor.dispatchCompaction to accept an
explicit kv.ReadTimestamp argument instead of relying on a voucher embedded in
context, and pass that value through the timestamp allocation and
operation-group dispatch. Update all callers, including the compaction dispatch
path, to provide the ReadTimestamp explicitly while preserving existing
normalization behavior.

In `@adapter/redis_expire_cmds.go`:
- Around line 95-99: readRedisStringAt のエラー処理を更新し、store.ErrKeyNotFound
の場合だけキー無しとして nil 値を返し、それ以外のストレージエラーやデコードエラーは呼び出し元へ伝播させてください。GETDEL
の削除処理と戻り値が非NotFoundエラーを隠さないよう維持し、該当ブロックの //nolint:nilerr も削除してください。

In `@adapter/redis_hash_cmds.go`:
- Around line 567-598:
重複した要素生成ロジックを共通ヘルパーへ抽出し、adapter/redis_hash_cmds.go:567-598 の
persistHashReadTimestampTxn と persistHashTxn
の両方から利用して、各関数はディスパッチに専念させてください。adapter/redis_collection_ttl.go:151-166 の処理も
dispatchCollectionExpire と要素生成を共通化し、旧関数の呼び出し元が残っていなければ削除してください。

In `@adapter/redis_lists.go`:
- Around line 102-106: Update the retry-reason string passed to
beginTxnReadTimestamp in listPushCoreWithDedup to identify the dedup path, using
a distinct message such as “redis list push (dedup): begin read timestamp”;
leave the non-dedup listPushCore message unchanged.

In `@adapter/redis_stream_cmds.go`:
- Around line 1334-1346: Move the single r.readTS() call in xreadCheckTypes
outside the key loop, store its result, and reuse that snapshot for every
keyTypeAtExpect invocation while preserving the existing error and wrong-type
handling.

In `@adapter/redis_txn.go`:
- Around line 471-495: Replace the manual WaitGroup, error channel, and
cancelOnce logic in the transaction lease-read flow with errgroup.WithContext,
using the derived group context for LeaseReadForKeyThrough and returning the
group’s first error. Remove now-unused synchronization imports and preserve
cancellation of remaining reads after the first failure.

In `@adapter/redis_zset_cmds.go`:
- Around line 1319-1321: Normalize r.blockWaitFallback once to
defaultRedisBlockWaitFallback when it is zero or negative, then reuse that
resolved duration in both the fast-mode check and waitForBlockedCommandUpdate.
Ensure both paths apply identical fallback semantics without changing the
existing safety behavior.

In `@adapter/s3_hlc_fence_test.go`:
- Line 259: Update seedS3ObjectForReadVoucherTest so context.Context is the
first parameter, using the signature and all call sites in the order
seedS3ObjectForReadVoucherTest(ctx, t, st, bucket, key). Ensure the helper and
its callers comply with the context-as-argument lint rule.
- Around line 221-222:
対象の2テスト、TestS3CommitUploadPartRechecksUploadAtLatestAppliedWatermark と
TestS3CommitUploadPartIncludesUploadMetaInReadSet の先頭で t.Parallel()
を呼び出し、同一ファイル内の他テストと同様に並列実行できるようにする。

In `@distribution/catalog_test.go`:
- Around line 368-373: Validate the route collection length before indexing it
in the SnapshotAt test. Add a require.Len assertion for snapshot.Routes before
the snapshot.Routes[0].RouteID check, preserving the existing assertions and
expected route count.

In `@kv/shard_store.go`:
- Around line 2339-2353: Replace the sort.Slice call in tsoCommitFloorGroupIDs
with slices.Sort(ids), preserving ascending uint64 ordering; apply the same
simplification to the LocalStores implementation around line 2633.

In `@kv/sharded_coordinator_txn_test.go`:
- Around line 386-388: 「TestReadTimestampVoucherBindingShadowsParentCapability」に
t.Parallel() を追加し、同ファイル内の他テストと並列実行の扱いを統一してください。
- Around line 484-485: Replace the require.Nil assertions around state.Apply in
the affected test with require.NoError, preserving both Apply calls and their
arguments so failures report the returned error details.

In `@kv/tso_fsm.go`:
- Around line 141-150: Update the cutover dispatch in Apply to use
bytes.HasPrefix instead of requiring bytes.Equal, allowing applyCutoverEntry to
perform the complete envelope-length validation. Keep applyCutoverEntry’s exact
validation and error handling unchanged, matching the applyPhaseDEntry flow.
- Around line 458-467: Improve readability in
TSOStateMachine.IsVolatileOnlyPayload by making the three OR-separated payload
checks explicit with parentheses or early returns, rather than relying on &&/||
precedence. Preserve the existing matching behavior for HLC lease,
allocation-floor, and phase-D payloads, as well as the tsoCutoverEnvelope check.

In `@kv/tso_raft_test.go`:
- Line 100: Replace the four HLC millisecond conversions currently suppressed
with //nolint:gosec by adding one file-local helper such as
testHLCFromWallMillis(t time.Time) uint64. Have the helper perform the
Unix-millisecond-to-uint64 conversion, update all four call sites to use it, and
remove the corresponding nolint directives while preserving the existing HLC
timestamp behavior.

In `@kv/tso_raft.go`:
- Around line 940-975: Add a bounded retry policy or backoff to the retry loop
in ShadowTimestampAllocator.nextAfter when legacyTS does not exceed
reservation.PreviousAllocationFloor, while preserving context cancellation
handling and successful return paths. Avoid unbounded per-iteration warning logs
by limiting or throttling the corresponding discard logging.
- Around line 665-679: In the GetTimestamp request flow, compute the validated
n-to-uint32 conversion once and reuse that variable for both the request Count
field and the response count comparison. Keep the existing overflow-suppression
justification on the single conversion and preserve the protocol validation
behavior.
- Around line 44-46: TSOReservationAllocator の ReserveBatchAfter
宣言に全引数の名前を追加し、特に2つの bool を activateCutover、activatePhaseD
の順序で明示してください。既存の型と戻り値は変更せず、呼び出し側が引数の意図を確認できる宣言に更新してください。

In `@kv/tso_test.go`:
- Around line 333-380:
4つのBeginReadTimestampThroughテストを1つのテーブル駆動テストに統合し、各ケースでphaseDActive、phaseDRequired、validateErrと期待する戻り値・呼び出し回数・検証値を定義してください。各サブテストでは既存の検証内容を維持し、t.Parallel()を追加してファイル内のテスト慣行に合わせてください。

In `@main_tso_routing_test.go`:
- Around line 248-259: Update mainTSOEngine.Propose to use
github.com/cockroachdb/errors for the unexpected TSO apply result instead of
fmt.Errorf, and adjust imports accordingly. Preserve the existing error message
and type formatting.

In `@main.go`:
- Around line 2122-2147: Change configureCoordinatorTSO to accept a single
kv.TSOCutoverFloorProvider parameter instead of the variadic floorProviders
argument, and pass that value directly to configureDedicatedCoordinatorTSO.
Update all callers, including the existing call site, to provide the single
floor provider explicitly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e0a05717-d126-47c2-ac27-ade840a29c79

📥 Commits

Reviewing files that changed from the base of the PR and between 97a42ba and c231fdb.

⛔ Files ignored due to path filters (3)
  • proto/distribution.pb.go is excluded by !**/*.pb.go
  • proto/distribution_grpc.pb.go is excluded by !**/*.pb.go
  • proto/service.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (65)
  • adapter/distribution_server.go
  • adapter/distribution_server_test.go
  • adapter/dynamodb_item_write.go
  • adapter/dynamodb_locks.go
  • adapter/dynamodb_migration.go
  • adapter/dynamodb_onephase_dedup_test.go
  • adapter/dynamodb_schema.go
  • adapter/dynamodb_transact.go
  • adapter/grpc.go
  • adapter/grpc_test.go
  • adapter/phase_d_voucher_test.go
  • adapter/redis_collection_ttl.go
  • adapter/redis_compat_helpers.go
  • adapter/redis_delta_compactor.go
  • adapter/redis_expire_cmds.go
  • adapter/redis_hash_cmds.go
  • adapter/redis_list_dedup_test.go
  • adapter/redis_lists.go
  • adapter/redis_lua_context.go
  • adapter/redis_lua_phase_d_test.go
  • adapter/redis_retry_test.go
  • adapter/redis_set_cmds.go
  • adapter/redis_stream_cmds.go
  • adapter/redis_strings.go
  • adapter/redis_txn.go
  • adapter/redis_zset_cmds.go
  • adapter/s3.go
  • adapter/s3_admin.go
  • adapter/s3_admin_objects.go
  • adapter/s3_hlc_fence_test.go
  • adapter/s3_multipart_complete.go
  • adapter/s3_put_object.go
  • adapter/s3_upload_part.go
  • adapter/sqs_catalog.go
  • adapter/sqs_fifo.go
  • adapter/sqs_messages.go
  • adapter/sqs_messages_batch.go
  • adapter/sqs_purge.go
  • adapter/sqs_reaper.go
  • adapter/sqs_redrive.go
  • adapter/sqs_tags.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • docs/design/2026_04_16_partial_centralized_tso.md
  • kv/coordinator.go
  • kv/keyviz_label.go
  • kv/leader_routed_store_test.go
  • kv/lease_warmup_test.go
  • kv/shard_store.go
  • kv/sharded_coordinator.go
  • kv/sharded_coordinator_txn_test.go
  • kv/tso.go
  • kv/tso_floor_test.go
  • kv/tso_fsm.go
  • kv/tso_fsm_test.go
  • kv/tso_raft.go
  • kv/tso_raft_test.go
  • kv/tso_test.go
  • main.go
  • main_encryption_admin.go
  • main_encryption_admin_test.go
  • main_encryption_rotate_on_startup_test.go
  • main_tso_routing_test.go
  • proto/distribution.proto
  • proto/service.proto
🚧 Files skipped from review as they are similar to previous changes (1)
  • main_encryption_admin_test.go

Comment on lines +244 to +252
resp := &pb.ValidateTimestampResponse{Valid: true, PhaseDActive: true}
if state, ok := s.timestampAllocator.(interface {
PhaseDFloor() uint64
AllocationFloor() uint64
}); ok {
resp.PhaseDFloor = state.PhaseDFloor()
resp.AllocationFloor = state.AllocationFloor()
}
return resp, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

PhaseDActive を無条件に true として返しています。

Line 244 は検証成功時に常に PhaseDActive: true を設定します。この値が正しいのは、ValidateDurableTimestamp が Phase D 非アクティブ時に必ず kv.ErrTSOPhaseDInactive を返す場合に限られます。この前提はコード上に明示されていません。

Line 245 のオプショナルインタフェースには既に PhaseDFloor()AllocationFloor() があります。ここに PhaseDActive() bool を加えて実際の状態を返すか、少なくとも前提をコメントで明記してください。adapter/distribution_server_test.go:1411 には PhaseDActive() の実装が既にあります。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/distribution_server.go` around lines 244 - 252, Update the
ValidateTimestamp response construction around ValidateDurableTimestamp to
derive PhaseDActive from the allocator state instead of always setting it to
true. Extend the optional timestamp allocator interface with PhaseDActive()
bool, assign the returned value to resp.PhaseDActive alongside PhaseDFloor() and
AllocationFloor(), and preserve the existing fallback behavior when the
allocator does not implement the interface.

Comment on lines 316 to +324
tickCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
readTimestamp, err := kv.BeginReadTimestampThrough(tickCtx, c.coord, snapshotTS(c.coord.Clock(), c.st),
"redis delta compactor: begin read timestamp")
if err != nil {
return errors.WithStack(err)
}
tickCtx = readTimestamp.WithDispatchVoucher(tickCtx)
readTS := readTimestamp.Timestamp()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# voucher の消費・解放契約を確定させる
set -euo pipefail
ast-grep outline kv/tso.go --items all
rg -n -C15 'appliedReadDispatchVoucher' --type=go
rg -n -C10 'func .*WithDispatchVoucher|func DispatchWithReadTimestamp|func BeginReadTimestampThrough|func .*beginTxnReadTimestamp' --type=go

Repository: bootjp/elastickv

Length of output: 7100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== kv/tso.go relevant lines =="
sed -n '80,190p' kv/tso.go

echo
echo "== dispatcher implementation and other tso functions =="
sed -n '195,330p' kv/tso.go

echo
echo "== adapter sites =="
sed -n '298,335p' adapter/redis_delta_compactor.go
sed -n '185,230p' adapter/redis_lists.go
sed -n '768,792p' adapter/redis_compat_helpers.go

echo
echo "== usage count / imports for ReadTimestamp voucher methods =="
rg -n '\.WithDispatchVouncer|WithDispatchVoucher|DispatchWithReadTimestamp|BeginReadTimestampThrough|RevokeAppliedReadTimestamp|VouchAppliedReadTimestamp' --type=go

Repository: bootjp/elastickv

Length of output: 13753


各 dispatch で ReadTimestamp voucher を再取得してください。 DispatchWithReadTimestamp は同じ ReadTimestamp を渡されない限り券を発行せず、BeginReadTimestampThrough の券は dispatch 単位で消費される設計です。現在の実装では、SyncOnce の 1 券を複数 dispatch に再利用しています。compactBackgroundHandlers が dispatch を呼ぶ直前に BeginReadTimestampThrough を読み直し、SyncOncetickCtx は各 BeginReadTimestampThrough 呼び出しごと生成するか、dispatch 毎に券付き context を作り直してください。

📍 Affects 3 files
  • adapter/redis_delta_compactor.go#L316-L324 (this comment)
  • adapter/redis_lists.go#L221-L222
  • adapter/redis_compat_helpers.go#L781-L784
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_delta_compactor.go` around lines 316 - 324, 各 dispatch ごとに
ReadTimestamp voucher を再取得するよう、adapter/redis_delta_compactor.go:316-324 の
SyncOnce と compactBackgroundHandlers の処理を更新してください。dispatch 直前に
BeginReadTimestampThrough を呼び、各呼び出しから voucher 付き context を生成して、同じ tickCtx や券を複数
dispatch で再利用しないでください。関連する adapter/redis_lists.go:221-222 および
adapter/redis_compat_helpers.go:781-784 でも同じ dispatch 単位の取得・context 生成に統一してください。

Comment thread adapter/s3.go
Comment on lines +2514 to +2530
func (s *S3Server) beginTxnReadTimestamp(ctx context.Context, readTS uint64, label string) (kv.ReadTimestamp, error) {
if readTS == ^uint64(0) {
if alloc, ok := kv.TimestampAllocatorThrough(s.coordinator); ok {
if phaseD, phaseDOK := alloc.(kv.TSOPhaseDState); phaseDOK && (phaseD.PhaseDRequired() || phaseD.PhaseDActive()) {
readTS = 1
readTimestamp, err := kv.BeginReadTimestampThrough(ctx, s.coordinator, readTS, label)
return readTimestamp, errors.WithStack(err)
}
}
}
startTS, err := s.txnStartTS(ctx, readTS)
if err != nil {
return kv.ReadTimestamp{}, err
}
readTimestamp, err := kv.BeginReadTimestampThrough(ctx, s.coordinator, startTS, label)
return readTimestamp, errors.WithStack(err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# beginTxnReadTimestamp を使う関数内に残る直接 Dispatch を検出する。
rg -nP --type=go -l 'beginTxnReadTimestamp' | while IFS= read -r f; do
  echo "=== $f ==="
  rg -nP -C30 'beginTxnReadTimestamp\(' "$f" \
    | rg -nP 'beginTxnReadTimestamp\(|coordinator\.Dispatch\(|DispatchWithReadTimestamp\(|WithDispatchVoucher\('
done
echo '=== all remaining direct dispatches in adapters ==='
rg -nP --type=go -C2 'coordinator\.Dispatch\(' -g 'adapter/**'

Repository: bootjp/elastickv

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate files ---'
git ls-files | rg '(^adapter/s3\.go$|^adapter/s3_admin\.go$|^adapter/sqs_messages\.go$|^adapter/sqs_fifo\.go$|^.*sharded_coordinator_txn_test\.go$)$' || true

echo '--- search beginTxnReadTimestamp definitions/usages ---'
rg -nP --type=go -n 'beginTxnReadTimestamp|BeginReadTimestampThrough|WithDispatchVoucher|DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter kv || true

echo '--- relevant line ranges ---'
for f in $(git ls-files | rg '(^adapter/s3\.go$|^adapter/s3_admin\.go$|^adapter/sqs_messages\.go$|^adapter/sqs_fifo\.go$)'); do
  echo "### $f"
  wc -l "$f"
done

echo '### adapter/s3.go lines 2400-2560'
sed -n '2400,2560p' adapter/s3.go | nl -ba -v2400

echo '### adapter/sqs_messages.go lines 960-1035'
sed -n '960,1035p' adapter/sqs_messages.go | nl -ba -v960

echo '### adapter/s3_admin.go relevant slices'
rg -nP -C20 'AdminPutBucketAcl|beginTxnReadTimestamp|coordinator\.Dispatch\(|DispatchWithReadTimestamp\(|WithDispatchVoucher\(' adapter/s3_admin.go || true

Repository: bootjp/elastickv

Length of output: 28942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '### S3 beginTxnReadTimestamp callers and direct dispatches'
grep -nP 'beginTxnReadTimestamp|DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter/s3.go

echo '--- lines 860-900 ---'
sed -n '860,900p' adapter/s3.go

echo '--- lines 620-690 ---'
sed -n '620,690p' adapter/s3.go

echo '--- lines 1520-1610 ---'
sed -n '1520,1610p' adapter/s3.go

echo '--- lines 1840-1860 ---'
sed -n '1840,1860p' adapter/s3.go

echo '### S3 admin beginTxnReadTimestamp caller and direct dispatch'
grep -nP 'AdminPutBucketAcl|beginTxnReadTimestamp|DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter/s3_admin.go

echo '--- lines 320-365 ---'
sed -n '320,365p' adapter/s3_admin.go

echo '### SQS messages receive path caller and lower dispatches'
grep -nP 'beginTxnReadTimestamp|scanAndDeliverPartition|DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter/sqs_messages.go

echo '--- lines 980-1045 ---'
sed -n '980,1045p' adapter/sqs_messages.go

echo '### SQS fifo dispatches'
grep -nP 'DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter/sqs_fifo.go

echo '### Admin objects relevant dispatch'
grep -nP 'beginTxnReadTimestamp|DispatchWithReadTimestamp|coordinator\.Dispatch\(' adapter/s3_admin_objects.go

echo '--- lines 240-280 ---'
sed -n '240,280p' adapter/s3_admin_objects.go

Repository: bootjp/elastickv

Length of output: 18004


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '### kv tso relevant implementation'
sed -n '90,180p' kv/tso.go

echo '### kv test around phase D direct dispatch rejection'
sed -n '240,310p' kv/sharded_coordinator_txn_test.go

Repository: bootjp/elastickv

Length of output: 6454


BeginReadTimestampThrough 経由の直接 dispatch を DispatchWithReadTimestamp に置き換えてください。 Phase-D 有効時、coord.Dispatch(ctx, req)ErrTSOTimestampPrePhaseDStartTS の有効性を拒否します。対応箇所は以下の直接 dispatch です。

  • adapter/s3_admin.go#L354: AdminPutBucketAclreadTimestamp.WithDispatchVoucher(ctx) + kv.DispatchWithReadTimestamp を使う必要があります
  • adapter/s3_admin.go#L474, #L518: admin delete/other 直接 dispatch も同様に置き換えてください
  • adapter/s3.go#L1530, #L1601, #L1850: cleanup/delete 直接 dispatch も開始時 read timestamp の voucher で dispatch する必要があります
  • adapter/s3_admin_objects.go#L266: multipart admin direct dispatch も同様に置き換えてください
📍 Affects 2 files
  • adapter/s3.go#L2514-L2530 (this comment)
  • adapter/sqs_messages.go#L1002-L1007
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/s3.go` around lines 2514 - 2530, Replace direct
BeginReadTimestampThrough-based dispatches with kv.DispatchWithReadTimestamp
using the read timestamp’s WithDispatchVoucher(ctx). Update AdminPutBucketAcl
and the admin delete/other dispatches in adapter/s3_admin.go (lines 354, 474,
and 518), cleanup/delete dispatches in adapter/s3.go (lines 1530, 1601, and
1850), and multipart admin dispatch in adapter/s3_admin_objects.go (line 266);
adapter/s3.go lines 2514-2530 requires the corresponding read-timestamp voucher
flow, and adapter/sqs_messages.go lines 1002-1007 requires the same replacement.
Preserve each operation’s existing request and error handling.

Comment on lines +3 to +7
- Status: Partial — M1-M7 are implemented, including the dedicated group-0
FSM, leader-routed durable windows, strict term bootstrap, serialized shadow
migration, one-way rolling cutover, durable Phase-D retirement, and
cross-shard SSI timestamp validation. Runtime config reload and production
latency/alerting work remain open.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

ライフサイクルマーカーの妥当性を確認してください。

行3 は「M1-M7 are implemented」と記載します。セクション8のマイルストーン表(行831-837)でも M1 から M7 までのすべてが shipped です。

コーディングガイドラインは、設計文書のファイル名マーカーを次のように定義します。*_partial_*.md は「some milestones shipped, full proposal not complete」、*_implemented_*.md は「all milestones shipped」です。

現在のファイル名は 2026_04_16_partial_centralized_tso.md です。すべてのマイルストーンが shipped であるため、2026_04_16_implemented_centralized_tso.md への変更が該当する可能性があります。

行144-147 の Remaining 項目(ランタイム設定リロード、本番ベンチマークとアラート閾値)をマイルストーンとして扱う方針であれば、セクション8の表に M8 として追加してください。そうすれば _partial_ の維持が表と整合します。どちらの方針を採るかを決めてください。

ファイル名を変更する場合は、他文書からの参照も更新が必要です。

#!/bin/bash
# この設計文書への参照箇所を洗い出す。
rg -n '2026_04_16_partial_centralized_tso' --glob '!**/node_modules/**'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/2026_04_16_partial_centralized_tso.md` around lines 3 - 7,
Resolve the lifecycle marker inconsistency for the design document: either
rename it to the implemented marker when M1–M7 are the complete milestone set,
updating every reference found by searching for the old filename, or define the
remaining runtime reload and production latency/alerting work as M8 in the
milestone table so the partial marker remains accurate. Keep the status text,
Remaining section, and milestone table consistent with the chosen policy.

Source: Coding guidelines

Comment on lines +62 to +63
decoded and deterministically rejected without halting the TSO apply loop;
malformed control entries still halt fail-closed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

リスト継続行のインデントが不足しています。

行63 の先頭インデントは3スペースです。項目10 は 10. で始まるため、継続行は4スペースのインデントが必要です。同じ項目の他の継続行(行54-62)は4スペースです。

3スペースのままでは、一部の Markdown レンダラが行63 を項目10 の継続として扱わず、独立した段落として表示します。

📝 提案する修正
     decoded and deterministically rejected without halting the TSO apply loop;
-   malformed control entries still halt fail-closed.
+    malformed control entries still halt fail-closed.
 11. `RaftTSOAllocator` verifies group-0 leadership and commits every returned
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/2026_04_16_partial_centralized_tso.md` around lines 62 - 63,
Increase the leading indentation of the continuation line beginning “malformed
control entries” to four spaces so it remains part of numbered item 10, matching
the indentation of the other continuation lines.

Comment thread kv/keyviz_label.go
Comment on lines +124 to +130
func (c keyVizLabeledCoordinator) VouchAppliedReadTimestamp(timestamp uint64, ref AppliedReadTimestampVoucherRef) error {
voucher, ok := c.inner.(AppliedReadTimestampVoucher)
if !ok {
return errors.WithStack(ErrTSOProtocolUnsupported)
}
return errors.WithStack(voucher.VouchAppliedReadTimestamp(timestamp, ref))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

装飾側が常に AppliedReadTimestampVoucher を実装するため、失敗の検出が遅れます。

keyVizLabeledCoordinator はこのメソッドを常に持ちます。そのため kv/tso.govalidateAppliedReadTimestamp(Line 352)の型アサーションは、内側の Coordinator が voucher に非対応でも成功します。結果として BeginReadTimestampThrough は voucher 付きの ReadTimestamp を返し、ErrTSOProtocolUnsupported は dispatch 時まで表面化しません。読み取り開始時点で失敗させるほうが安全です。内側の対応可否を問い合わせる小さなヘルパー(例: supportsAppliedReadVoucher())を公開し、validateAppliedReadTimestamp 側でそれを使う方法を検討してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/keyviz_label.go` around lines 124 - 130, Add an exported capability check
on keyVizLabeledCoordinator, such as supportsAppliedReadVoucher, that reports
whether inner implements AppliedReadTimestampVoucher rather than relying on the
wrapper’s method set. Update validateAppliedReadTimestamp in tso.go to use this
check and reject unsupported coordinators before BeginReadTimestampThrough
returns a voucher-bearing ReadTimestamp, while preserving the existing dispatch
behavior.

Comment thread kv/shard_store.go
Comment on lines +2419 to +2430
if isLeaderEngine(engine) {
if ts, err := s.GroupCommittedTimestampFloor(ctx, groupID); err == nil {
return ts, nil
}
// Leadership may have changed between State and ReadIndex. Resolve the
// newly published leader below instead of trusting the local watermark.
}
addr := leaderAddrFromEngine(engine)
if addr == "" {
return 0, errors.Wrapf(ErrTSOCommitFloorUnavailable,
"data group %d has no known leader", groupID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

ローカルリーダーの fence 失敗時に自ノードへ RPC を再送する可能性があります。

Line 2419 でこのノードがリーダーだと判定し、GroupCommittedTimestampFloor の ReadIndex が失敗した場合、Line 2426 で leaderAddrFromEngine にフォールバックします。リーダー情報がまだ更新されていなければ、この値は自ノードのアドレスです。その場合、自ノードへ gRPC 往復してから同じ GroupCommittedTimestampFloor を再実行し、同じ理由で失敗します。無限ループにはなりませんが、TSO term 初期化の失敗検知が proxyForwardTimeout 分だけ遅延します。

解決したアドレスが自ノードのものであるかを判定し、その場合は元の fence エラーを直接返す方が速く失敗できます。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/shard_store.go` around lines 2419 - 2430, Update the fallback flow around
GroupCommittedTimestampFloor and leaderAddrFromEngine so that, after a local
leader fence failure, it detects when the resolved leader address belongs to the
current node and immediately returns the original fence error instead of
forwarding an RPC to itself. Preserve the existing remote-leader forwarding
behavior for addresses belonging to other nodes.

Comment thread kv/tso_fsm.go
Comment on lines +433 to +439
func hasHeaderlessLegacyKVFSMSnapshotPayload(br *bufio.Reader) (bool, error) {
peeked, err := br.Peek(tsoSnapshotV4Len + 1)
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
return false, errors.Wrap(err, "tso fsm snapshot: peek headerless legacy payload")
}
return len(peeked) > tsoSnapshotV4Len, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# ヘッダなし旧kvFSMスナップショットの実際の形式と長さ前提を確認する
rg -n -C5 'hasHeaderlessLegacyKVFSMSnapshotPayload|restoreHeaderlessLegacyKVFSMSnapshot' --type=go
rg -n -C8 'legacyKVFSMStoreSnapshotMagics|EKVMVCC2|EKVPBBL1|EKVSSTI1' --type=go
rg -n -C5 'hlcSnapshotMagic|isV1Magic|isV2Magic|isUnknownEKVTHLC' --type=go

Repository: bootjp/elastickv

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -u
printf 'repo files around kv/tso_fsm.go:\n'
git ls-files | rg '(^|/)tso_fsm\.go$|(^|/)kv/' | sed -n '1,120p'

printf '\nFind functions/consts names broadly:\n'
rg -n 'Legacy|legacy|Headerless|snapshot|tsoSnapshot|KVFSM|SnapshotPayload|\bEKV\b|HLC|Magic' . --glob '*.go' --glob '!vendor/**' --glob '!**/testdata/**' | sed -n '1,240p'

Repository: bootjp/elastickv

Length of output: 27916


🏁 Script executed:

#!/bin/bash
set -u
printf 'tso_fsm.go size and relevant lines:\n'
wc -l kv/tso_fsm.go
sed -n '400,470p' kv/tso_fsm.go

printf '\nAll exact identifiers in all files:\n'
rg -n 'hasHeaderlessLegacyKVFSMSnapshotPayload|restoreHeaderlessLegacyKVFSMSnapshot|legacyKVFSMStoreSnapshotMagics|ReadTSOSnapshotState|readTSOSnapshotState|tsoSnapshotV4Len|EKVMVCC2|EKVPBBL1|EKVSSTI1|hlcSnapshotMagic|isV1Magic|isV2Magic|isUnknownEKVTHLC' . --glob '*.go' --glob '!vendor/**' --glob '!**/testdata/**' || true

Repository: bootjp/elastickv

Length of output: 10767


🏁 Script executed:

#!/bin/bash
set -u
sed -n '1,120p' kv/tso_fsm.go
sed -n '250,390p' kv/tso_fsm.go
sed -n '470,510p' kv/tso_fsm_test.go
printf '\nConstants across codebase:\n'
rg -n 'tsoSnapshotV[0-9]Len|hlcSnapshotMagic|PebbleSnapshotMagicLen|legacyKVFSMStoreSnapshotMagics|Restore\(' kv/tso_fsm.go kv/tso_fsm_test.go internal/raftengine/etcd/persisted_snapshot_export_test.go

Repository: bootjp/elastickv

Length of output: 17276


🏁 Script executed:

#!/bin/bash
set -u
rg -n 'const|hlcLeasePayloadLen|hlcLeaseEntryLen|PebbleSnapshotMagicLen|ReadSnapshotHeader' kv internal/backup --glob '*.go'
printf '\nReadSnapshotHeader implementation:\n'
sed -n '95,155p' internal/backup/snapshot_reader.go
printf '\nStoreSnapshot implementations mentioning length/magic:\n'
sed -n '3060,3160p' store/lsm_store.go
sed -n '3215,3280p' store/lsm_store.go

Repository: bootjp/elastickv

Length of output: 49557


🏁 Script executed:

#!/bin/bash
set -u
python3 - <<'PY'
from pathlib import Path
import re

p = Path('kv/tso_fsm.go')
text = p.read_text()

m = re.search(r'const\((.*?)\)', text, re.S)
const_block = m.group(1) if m else ''
print('const block contains tsoSnapshot constants:')
for part in ['tsoAllocationFloorEnvelope', 'tsoCutoverEnvelope', 'tsoPhaseDEnvelope', 'tsoSnapshotV1Len', 'tsoSnapshotV2Len', 'tsoSnapshotV3Len', 'tsoSnapshotV4Len']:
    print(part, bool(re.search(rf'\b{part}\b(?!.*=)', const_block)))

# Behavioral probe of current length-only predicate for truncated/corrupt payloads.
# hlcLeasePayloadLen = 8; tsoSnapshotV4Len computed from source constants below.
hlc_lease_payload_len = 8
tso_snapshot_v1_len = hlc_lease_payload_len
tso_snapshot_v2_len = hlc_lease_payload_len * 2
tso_snapshot_v3_len = tso_snapshot_v2_len + 1
tso_snapshot_v4_len = tso_snapshot_v3_len + 1 + hlc_lease_payload_len

print('computed tsoSnapshotV4Len=', tso_snapshot_v4_len)
print('computed hlcLeasePayloadLen=', hlc_lease_payload_len)

payloads = {
    'truncated_unknown_25': bytearray(25),
    'invalid_ekvthlc_25': b'EKVTHLC' + bytes(18),
    'invalid_kvfsm_magics_25': b'EKVPBBL1' + bytes(17),
    'valid_exact_v4': bytearray(tso_snapshot_v4_len),
    'short_valid_v4': bytearray(tso_snapshot_v4_len - 1),
}
for name, payload in payloads.items():
    peeked_len = min(len(payload), tso_snapshot_v4_len + 1)
    result = peeked_len > tso_snapshot_v4_len
    print(name, len(payload), 'headerless_legacy_predicate=', result)
PY

printf '\nWriteTo implementation and constants around kvFSM snapshot write:\n'
sed -n '35,90p' kv/fsm.go
sed -n '540,630p' kv/tso_fsm.go
sed -n '690,735p' kv/fsm.go

Repository: bootjp/elastickv

Length of output: 8533


ヘッダなし旧 kvFSM スナップショットは魔法値で判定してください。

len(peeked) > tsoSnapshotV4Len(>26バイト)だけで旧形式を判定すると、破損または不正な長尺 TSO スナップショットを restoreHeaderlessLegacyKVFSMSnapshot が全量 discard します。このままだと readTSOSnapshotState の長さ検証を通らず、ceiling と allocation floor が 0 のまま復元成功になります。判定条件は restoreLegacyKVFSMSnapshot と同じく EKVMVCC2EKVPBBL1EKVSSTI1 のヘッダに限定するか、長さ上限と魔法値を併用してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso_fsm.go` around lines 433 - 439, Update
hasHeaderlessLegacyKVFSMSnapshotPayload to identify headerless legacy snapshots
using the recognized magic headers EKVMVCC2, EKVPBBL1, or EKVSSTI1, matching
restoreLegacyKVFSMSnapshot, rather than relying only on len(peeked) >
tsoSnapshotV4Len. Preserve the existing peek error handling and return false for
oversized payloads without a recognized legacy header.

Comment thread kv/tso.go
Comment on lines +179 to +199
func (v *appliedReadDispatchVoucher) prepare(coord Coordinator, timestamp uint64) (ReadTimestamp, func(), error) {
v.mu.Lock()
defer v.mu.Unlock()
voucher, ok := coord.(AppliedReadTimestampVoucher)
if !ok {
return ReadTimestamp{}, nil, errors.WithStack(ErrTSOProtocolUnsupported)
}
preparedVoucher := newAppliedReadDispatchVoucher()
if err := voucher.VouchAppliedReadTimestamp(timestamp, preparedVoucher.ref); err != nil {
return ReadTimestamp{}, nil, errors.WithStack(err)
}
v.prepared++
revoke := func() {}
if revoker, ok := coord.(AppliedReadTimestampVoucherRevoker); ok {
ref := preparedVoucher.ref
revoke = func() {
revoker.RevokeAppliedReadTimestamp(timestamp, ref)
}
}
return ReadTimestamp{timestamp: timestamp, voucher: preparedVoucher}, revoke, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

voucher の使用回数契約が未確定です。 appliedReadDispatchVoucher.prepareprepared を加算しますが、その値を読む箇所も上限判定もありません。ErrTSOReadVoucherLimit も未使用です。そのため 1 つの ReadTimestamp から dispatch を何度でも実行できます。アダプタ側はこの緩い契約に依存しています。

  • kv/tso.go#L179-L199: 上限を実装するか、preparedErrTSOReadVoucherLimit を削除して「無制限に再利用可能」という契約を doc コメントへ明記してください。
  • adapter/sqs_reaper.go#L76-L93: 1 つの ReadTimestamp を 1 パス内の多数の削除 dispatch で再利用しています。上限を実装する場合は、リーパーが上限に達しない設計(レコード単位で ReadTimestamp を取得するなど)へ変更してください。
📍 Affects 2 files
  • kv/tso.go#L179-L199 (this comment)
  • adapter/sqs_reaper.go#L76-L93
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/tso.go` around lines 179 - 199, Define and enforce a clear voucher-use
contract: in kv/tso.go lines 179-199, either implement the prepared-count limit
using ErrTSOReadVoucherLimit or remove prepared and the unused error and
document unlimited ReadTimestamp reuse; if enforcing a limit, update
adapter/sqs_reaper.go lines 76-93 so each deletion dispatch obtains a
ReadTimestamp without exceeding that limit, such as acquiring one per record.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c231fdbb50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/tso_raft.go
if err := validateTSOMinimumWindow(floor, n); err != nil {
return 0, 0, err
}
base := floor + 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep Phase-D timestamps tied to wall time

When Phase D is active, advancing directly from floor ignores both the current wall clock and the HLC's newer physical ceiling. After an idle interval, the next timestamp therefore retains the previous allocation's physical millisecond, and that component advances by only one millisecond per 65,536 issued timestamps. This breaks consumers that interpret the HLC physical bits as time: for example, S3 stores the commit timestamp in LastModifiedHLC and adapter/s3.go:2473-2481 converts those bits directly into HTTP dates, so post-idle PUTs can report arbitrarily stale Last-Modified values. Preserve the contiguous validation property without freezing the physical component, such as by tracking committed allocation ranges instead of treating every integer gap as issued.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

## Summary

- add atomic runtime reload for the one-way `legacy -> shadow -> cutover
-> phase-d` sequence
- preserve durable cutover and Phase-D overrides while rejecting
operator-requested rollback and phase skipping
- export bounded TSO latency, shadow divergence, mode, reload, and
durable-state metrics with checked Prometheus alerts
- add a 16-writer, 1/3/8-way fanout benchmark and record three-run
batch-size evidence
- promote the centralized TSO design from partial to implemented and
update all references

## Safety

Legacy mode keeps the coordinator allocator logically absent, preserving
existing HLC fallback callers. Cutover activation is published before
batch invalidation, durable group-0 markers can only advance the local
mode, and invalid reloads leave the active allocator unchanged.

## Validation

- `go test ./kv ./monitoring . -count=1`
- `go test -race ./kv ./monitoring . -count=1`
- `golangci-lint run ./... --timeout=5m --allow-parallel-runners`
- `go test ./kv -run ^'$' -bench ^'BenchmarkTSOWriteFanout$' -benchmem
-benchtime=1s -count=3`\n- `promtool check config
monitoring/prometheus/prometheus.yml` (8 rules)\n- `go test ./store
-count=1 -timeout=15m`\n- `git diff --check`\n- caller audit across
allocator providers, coordinator fields, runtime flags, activation, and
Phase-D state\n- zero references to the former partial design path
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (96c7573):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant