Skip to content

Coordinator: keep the first minted pending payload on concurrent next() - #152

Merged
thetangstr merged 1 commit into
mainfrom
fix/pending-mint-race
Sep 21, 2026
Merged

thetangstr merged 1 commit into
mainfrom
fix/pending-mint-race

Conversation

@thetangstr

Copy link
Copy Markdown
Owner

Summary

  • Fix a coordinator race in agent_handshake_next: two concurrent polls for the same role could both observe no pending payload and mint different proposals/acceptances/evidence; the last store.update won, stranding signatures made over the loser's request bytes (artifact_signer on submit_checkpoint, deterministic on retry).
  • The mint decision now happens inside the serialized store.update callback — an existing stored pending is kept, and the response's signing request is always built from the stored bytes. Applied to proposal, acceptance, evidence, and the proposal-window renewal path.
  • Checkpoint failure detail tags now carry bounded diagnostics (artifact_signer.<recovered12>.<bytesHash8>, artifact_digest.<expected8>, checkpoint_signer.<recovered12>) in the server-side agent_handshake_tool_failure log only — public errors stay coordination_failed.

Evidence

  • Production log for session 9eeb53e9 showed detail: "artifact_signer" on all three acceptance submit_checkpoint retries.
  • Two new regression tests gate the first mint's update so a concurrent poll commits a different payload first; they fail on pre-fix code and pass with the fix.
  • Full suite: 550/550.

Test plan

  • npm run build (mcp-server)
  • npm test — 550/550
  • Red/green: new tests fail on the pre-fix coordinator
  • Live two-agent run on /next to VERIFIED after deploy

Two concurrent next() calls for the same role can both observe no pending
payload and each mint a fresh one; the last store.update wins, so the losing
caller holds a signing request over bytes the coordinator no longer stores.
Any checkpoint signed from that stale request then fails artifact_signer on
submit — deterministically, on every retry.

Serialize the decision inside the update callback: if a pending payload is
already stored, keep it and build the response signing request from the
stored bytes. Covers proposal, acceptance, evidence, and the proposal-window
renewal path.

Also extend the checkpoint failure detail tags with the recovered signer
address prefix and the pending bytes hash prefix (session-scoped public
data) so a residual artifact_signer failure separates wrong-key from
wrong-bytes in one log line.
@thetangstr
thetangstr merged commit a100acd into main Sep 21, 2026
2 checks passed
@thetangstr
thetangstr deleted the fix/pending-mint-race branch September 21, 2026 07:05
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