Recovery semantics: owned-stranded vs handed-out (clawback) — encode the class in code, give clawback its own verb and policy
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 25/100
Línea de trabajo
Start by reading src/tollwallet/port.go and src/cli/server.go for the existing recovery contract, then compare the manual clawback flow in scripts/token-recovery/main.go with src/upstream_session_manager/token_recovery.go. The issue depends on #549, #500, #502, and #423 and calls for new policy, ownership coordination, dispute handling, and crash consistency. Done means the new clawback path is distinct from wallet recover and passes the specified unit, integration, and failure-injection tests.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Priority: P2 — recovery semantics: owned-stranded vs handed-out (clawback). Encode the distinction in code; give clawback its own verb and policy. Companion to #500, downstream of #549. Explicitly post-0.6.0.
Problem
NUT-07 liveness checks serve two kinds of recovery with opposite verdict semantics, and the codebase already implements both without naming the split:
- Owned-stranded recovery (type 1) — tokens that are ours, stuck mid-operation by a failure: drain-journal entries (#375/#433, recovered by #549's
wallet recover), proofs reserved in the gonuts pending bucket (#500'sReclaimUnspentProofs). - Handed-out clawback (type 2) — tokens we gave to someone else whose payment then failed:
scripts/token-recoveryovertokens-to-recover.txt(written byupstream_session_manager/token_recovery.goon autopay failures), clientd rejections (#423), reseller interrupted hand-offs.
The mint cannot tell these apart — same checkstate call — but the meaning of each verdict inverts:
| Verdict | Owned-stranded (type 1) | Handed-out (type 2) |
|---|---|---|
UNSPENT |
stranded value; re-import, races nobody | recipient has not redeemed — clawback window open; re-spending is a race against their redemption |
SPENT |
already secured; nothing to do | the counterparty took the value while we got nothing — a dispute, not a closed case |
PENDING |
re-check later (in-flight op) | their redemption is literally in flight — cannot and should not act |
| unknown/error | re-run later | must not sweep; after a partial sweep, ambiguity is monetary (ErrSidecarAmbiguous class), not an incomplete report |
#549 documents this taxonomy on the WalletPort.CheckTokenSpendable seam and keeps wallet recover strictly type-1. But only type 1 has an in-tree, policy-governed path. Type 2 today is an out-of-tree script run by hand, with no grace window, no ownership coordination with #500's sweeper, no dispute record, and no crash story of its own — even though Receive()-back-to-wallet is itself an irreversible operation.
Why it matters
Conflating the two classes produces one of two failures: treating a clawback verdict as simple recovery (double-pay — voiding a slow-but-honest recipient's payment), or treating "spent" as "case closed" when it means the counterparty kept the money. Type-2 actions are spends; running them under a read-only report's exit-code contract ("unknown exits non-zero") is category confusion — a partial sweep must surface as monetary ambiguity, not as a re-runnable report.
Current behavior (source refs)
src/tollwallet/port.go—CheckTokenSpendabledoc comment states the two-semantics contract (#549, 0cbe21d).src/cli/server.gohandleWalletRecover— type-1 only, ownership assumption explicit.scripts/token-recovery/main.go— manual type-2 tool: bounded checkstate,Wallet.Receive()on UNSPENT, dry-run mode.src/upstream_session_manager/token_recovery.go— writestokens-to-recover.txton autopay failure.- gonuts
ReclaimUnspentProofs/GetPendingMeltQuotes— type-1 machinery, never invoked by the daemon (#500). - #423 — clientd burns tokens on rejected payments; no reclaim path at all.
Desired invariant
Every recovery action declares its class. Owned recovery may report (and, per #500, reclaim automatically within our own wallet). Handed-out clawback is a separate verb — a spend that races the recipient — governed by an explicit policy: grace window, ownership markers, dispute surfacing, and its own crash-consistency (the clawback swap itself needs durable intent before the irreversible step).
Proposed scope
- Name the class at the seams:
RecoveryClass(owned vs handed-out) or distinct verbs —wallet recover(report, type 1) vswallet reclaim(sweep, type 2).recovermust never sweep handed-out tokens. - In-process type-2 path replacing the ad-hoc script for
tokens-to-recover.txt: checkstate → still unspent past the grace window → swap-to-self → record; spent → mark dispute/owed (feeds the business-transaction record #502). - Policy state: per-token grace window; ownership markers so the #500 sweeper and an in-flight reseller hand-off never fight over the same proofs (extends #500 scope item 4).
- clientd alignment (#423): same class distinction client-side.
- Exit-code/output contract for sweeps: any unknown after a partial sweep is reported as monetary ambiguity with per-token state, not a generic non-zero.
Acceptance criteria
- Fault test: token handed out, recipient never redeems → after grace, reclaim succeeds and the recipient's later redemption fails cleanly with a surfaced dispute record.
- Fault test: recipient redeems during the grace window → no double effect; state becomes dispute.
- Unknown at sweep time → no sweep attempted, retried next interval, no crash-loop.
Required tests
- Unit: class routing (recover never sweeps); grace-window expiry; ownership-marker skip shared with #500.
- Integration (cloud-lab): interrupted autopay lane → automated reclaim; dispute state when upstream did redeem.
Failure-injection tests
- Wedged mint during clawback check (bounded, #525/#532 idiom) — no sweep on unknown.
- Recipient redemption racing the reclaim swap (
-race). - SIGKILL between clawback intent persistence and the swap (the clawback needs its own crash story — journal or pending-bucket entry before the irreversible step, per the AGENTS.md fund-safety rules).
Compatibility
New verbs/state only; wallet recover behavior unchanged.
Dependencies
#549 (seam + taxonomy docs, merged first), #500 (sweeper interplay), #502 (dispute records), #423 (clientd).
Out of scope
Changing when tokens enter tokens-to-recover.txt (each flow's own fix); NUT-07 protocol changes; the #497 swap-crash-window saga work (separate track).
- Lenguaje dominante
- Go
- Estrellas
- 12
- Forks
- 14
- Merge medio
- 3 d 20 h
- PR fusionados (30 d)
- 261
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de OpenTollGate/tollgate-module-basic-go
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
OpenTollGate/tollgate-module-basic-go#1159 ·
Los mantenedores suelen responder en 1 día
-
Degraded-mode hygiene follow-ups: dead walletNotInitializedError() + main.go's startup log still over-promises mint recoveryPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
OpenTollGate/tollgate-module-basic-go#1135 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
setup_uhttpd_portal hardcodes the GUEST portal docroot to the TollGate path — the brand token cannot select a re-brand guest portalPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
OpenTollGate/tollgate-module-basic-go#1122 ·
Los mantenedores suelen responder en 1 día
-
changelog: dependency-security sweep missing from [Unreleased] [Amperstrand fork #105 review debt]Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
OpenTollGate/tollgate-module-basic-go#1055 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
OpenTollGate/tollgate-module-basic-go#1027 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de OpenTollGate/tollgate-module-basic-go
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
apache/arrow-swift#209 ·
Los mantenedores suelen responder en 1 día
-
orchestrator: NFS-cache "cache path is not set" WARN logged on every build/launch instead of onceAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 2 días
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 65/100
influxdata/telegraf#20031 ·
Los mantenedores suelen responder en 1 día
-
pkg/registry/file no longer builds for GOOS=windows: unguarded syscall.Stat_t in qualified_resource_migration.goPosiblemente ocupada @AnshulPatil2005 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
OneBusAway/maglev#1914 ·
Los mantenedores suelen responder en 2 días