Skip to content

fix(device-client): harden the reveal, request paths and ended approvals - #134

Merged
kinqsradio merged 2 commits into
mainfrom
fix/approval-hardening
Nov 12, 2026
Merged

kinqsradio merged 2 commits into
mainfrom
fix/approval-hardening

Conversation

@kinqsradio

Copy link
Copy Markdown
Member

Summary

  • A network error during the nonce reveal in approval_state no longer cancels the approval: the reveal is sent up to 3 times, asking the server first whether it already holds it (lost answer); a failure that outlasts the tries is Transport and the same approval goes on. The engine's reveal is kept until the server has it. Only a real mismatch cancels.
  • ApproverState::Digits has a manual Debug (Digits { .. }): digits and fingerprint never print.
  • Ids in request paths are percent-encoded (approvals.rs; sync.rs already did; client.rs has none): a/b?c becomes a%2Fb%3Fc.
  • deny_approval reads the approval first, so an ended one (expired, answered, used, cancelled) is ApprovalExpired with no second cancel sent; confirm_approval already gets the server's 410. Nothing is approved.
  • New DeviceClient::time_left(&approval): the server already returns expires_at (RFC 3339, UTC) in ApprovalCreated and PendingApproval; minus this device's clock; zero once passed, None if unreadable. No server change.
  • docs/device-client.md: "Mapping an approve/deny list onto the client" (one code sketch). Deny carries no reason yet.

Testing

  • Full gate with Postgres: fmt, clippy, structure, wasm32, workspace tests (984 pass).
  • New scripted-transport tests (tests/approvals.rs), unit tests for paths and times, and a real-server test for confirm/deny after the new device cancelled.
  • Mutation checks: 8 mutations; all killed after adding a mismatch-cancels test.
  • Not tested: the CI clippy toolchain (local only).

Co-authors

none

AI usage

  • Written by hand, with no AI
  • Harness and version: Claude Code 2.1.317
  • Models: claude-sonnet-5-5 (all)
  • Effort: medium
  • Human role: set scope and reviews the client API
  • Sources: none

Checklist

  • Works on Windows, macOS and Linux
  • docs/architecture.md with any rule change; generated wire types with any contract change
  • No copied source, secrets, credentials or personal data; no AI attribution in commits, title or branch

…rovals

A network failure during the reveal is retried (bounded) and never
cancels the approval; only a real mismatch does. ApproverState's Debug
hides the digits and the fingerprint. Ids in request paths are
percent-encoded. confirm and deny on an ended approval give
ApprovalExpired and never approve. The approver can read the time left
from the expiry the server already returns.
A reveal whose answer was lost, followed by a failed read, left the
approver stuck: every later send got 409 and the digits never showed.
The server is now asked whether it holds the nonce before a kept reveal
is sent again and after a 409, and held counts as sent.
@kinqsradio kinqsradio changed the title fix(device-client): harden the approver reveal, paths and ended approvals fix(device-client): harden the reveal, request paths and ended approvals Nov 12, 2026
@kinqsradio
kinqsradio merged commit 5291891 into main Nov 12, 2026
19 of 20 checks passed
@kinqsradio
kinqsradio deleted the fix/approval-hardening branch November 12, 2026 01:22
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