From fe019ce1e74e2c2281a96a339965b63ea253092f Mon Sep 17 00:00:00 2001 From: darepo-doc-bot Date: Fri, 10 Jul 2026 07:39:17 +0000 Subject: [PATCH] docs: nightly doc-gardening sweep 2026-07-10 Automated sweep via .claude/skills/doc-gardening. Updates per-package CLAUDE.md/AGENTS.md, docs/index.md entries, and ARCHITECTURE.md as needed. Workflow run: https://github.com/lightninglabs/darepo-client/actions/workflows/doc-gardening-nightly.yml --- darepod/AGENTS.md | 10 ++++++++++ darepod/CLAUDE.md | 10 ++++++++++ db/AGENTS.md | 7 +++++-- db/CLAUDE.md | 7 +++++-- vhtlcrecovery/coordinator/AGENTS.md | 5 +++++ vhtlcrecovery/coordinator/CLAUDE.md | 5 +++++ vtxo/AGENTS.md | 7 ++++++- vtxo/CLAUDE.md | 7 ++++++- 8 files changed, 52 insertions(+), 6 deletions(-) diff --git a/darepod/AGENTS.md b/darepod/AGENTS.md index f26373a3f..90c889b16 100644 --- a/darepod/AGENTS.md +++ b/darepod/AGENTS.md @@ -68,6 +68,16 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep exit-policy map (`recoveryExitPolicies`, built from the recovery store) and re-admits each orphaned recovery target under its own vHTLC exit policy rather than mislabeling it as a standard timeout. +- `recoverOrphanedUnrollJobs` recovers the exit *policy* but not the exit + *trigger*. A target force-exited under `TriggerFraudSpend` that crashed in + the gap between the VTXO status flip and the registry admission has no + registry record, so it re-admits as `TriggerRestart`. The only effect is + that its ready checkpoints broadcast immediately instead of deferring to + the recipient's fraud backstop window (`unroll.shouldSubmitReadyFrontier`): + earlier fees, same funds outcome, no missed deadline. A faithful fix would + stamp the trigger onto the VTXO row in the same transaction that flips it + to `UnilateralExit`; tracked as a separable follow-up in + darepo-client#914. - The chain-resolver→unroll bridge (`ensureUnrollFromExpiring`) maps a VTXO `ExpiringNotification`'s trigger and optional exit policy into the registry's `EnsureUnrollRequest`. `unrollStartTrigger` converts the string-typed diff --git a/darepod/CLAUDE.md b/darepod/CLAUDE.md index f26373a3f..90c889b16 100644 --- a/darepod/CLAUDE.md +++ b/darepod/CLAUDE.md @@ -68,6 +68,16 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep exit-policy map (`recoveryExitPolicies`, built from the recovery store) and re-admits each orphaned recovery target under its own vHTLC exit policy rather than mislabeling it as a standard timeout. +- `recoverOrphanedUnrollJobs` recovers the exit *policy* but not the exit + *trigger*. A target force-exited under `TriggerFraudSpend` that crashed in + the gap between the VTXO status flip and the registry admission has no + registry record, so it re-admits as `TriggerRestart`. The only effect is + that its ready checkpoints broadcast immediately instead of deferring to + the recipient's fraud backstop window (`unroll.shouldSubmitReadyFrontier`): + earlier fees, same funds outcome, no missed deadline. A faithful fix would + stamp the trigger onto the VTXO row in the same transaction that flips it + to `UnilateralExit`; tracked as a separable follow-up in + darepo-client#914. - The chain-resolver→unroll bridge (`ensureUnrollFromExpiring`) maps a VTXO `ExpiringNotification`'s trigger and optional exit policy into the registry's `EnsureUnrollRequest`. `unrollStartTrigger` converts the string-typed diff --git a/db/AGENTS.md b/db/AGENTS.md index 890238f8c..1b6024c04 100644 --- a/db/AGENTS.md +++ b/db/AGENTS.md @@ -29,8 +29,11 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db. int64` or `> MaxSatoshi`), and tapscript derivation failures all return success without persisting — they cannot crash the actor or block the indexer push stream. Real DB lookup/save errors are surfaced. diff --git a/vtxo/CLAUDE.md b/vtxo/CLAUDE.md index 4897749d1..801998cc2 100644 --- a/vtxo/CLAUDE.md +++ b/vtxo/CLAUDE.md @@ -83,6 +83,11 @@ when the local wallet owns the receive script. - `VTXOsMaterializedNotification` — Manager-facing notification carrying already-persisted descriptors; the manager spawns one actor per descriptor without performing another store write. Used by both the OOR receive path and the new incoming round VTXO handler. - `LazyChainResolver` — Forwarding `TellOnlyRef[ExpiringNotification]` that buffers notifications until `Set()` wires the real chain-resolver target. Breaks the init-order dependency between the VTXO manager (which spawns `LazyChainResolver` at startup) and the unroll registry (which is wired after the VTXO manager starts). Buffered notifications are replayed in-order on `Set()`. - `RefreshFeeQuoter` — Function type `func(ctx, amount btcutil.Amount, remainingBlocks uint32) btcutil.Amount`. Optional hook on `VTXOActorConfig`; invoked as an **advisory preview** before each auto-refresh emission to estimate the per-input operator fee for UX surfaces. Under the seal-time fee handshake (#270) the server is the binding fee authority — the quoter's return value is no longer attached to the wire intent. Nil quoter (legacy and test paths) yields `OperatorFee=0` on the harness-local `RefreshVTXORequest`, which has no effect on the round protocol. +- `ErrVTXONotFound` — Domain-level sentinel `VTXOStore.GetVTXO` returns when + the store has no record of the outpoint. Callers (e.g. the manager's + force-unroll path) match on this instead of a persistence-layer error like + `sql.ErrNoRows`, so the store decides how it signals a miss without the + manager depending on how it is backed. ## Relationships @@ -139,7 +144,7 @@ when the local wallet owns the receive script. - `ForceUnrollEvent` unifies every unilateral-exit trigger (manual `Unroll` RPC, fraud spend, vHTLC recovery) behind the manager's admission gate. It carries a `Trigger actormsg.UnrollTrigger` (zero value admits as critical expiry) and an `ExitPolicy fn.Option[actormsg.ExitPolicy]` (None selects the standard VTXO timeout policy); both ride through to the emitted `ExpiringNotification` so the chain-resolver bridge admits the registry job under the right `StartTrigger` and persists the correct exit-spend policy. It is accepted in `LiveState`, `PendingForfeitState`, `SpendingState`, and `ForfeitingState`: each transitions to `UnilateralExitState` and emits `ExpiringNotification` (trigger + exit policy threaded through) + `VTXOStatusUpdate{UnilateralExit}`. It does **not** emit `VTXOTerminatedNotification` on intent — `UnilateralExitState` is **non-terminal**, so the actor stays alive to observe the exit. Truly terminal states (`Spent`, `Forfeited`, `Failed`) self-loop; the manager maps that self-loop back to `ForceUnrollResponse{Accepted: false, Reason: "already terminal"}`. A `ForceUnrollEvent` on a VTXO already in `UnilateralExitState` is an idempotent re-admission, not a no-op: the actor stays in `UnilateralExitState`, does not re-persist the status, and **re-emits** the `ExpiringNotification` under the same trigger/policy so the chain-resolver bridge re-admits the job (the first admission's best-effort Tell can be lost to a crash before the registry writes its record; the registry dedups against a live record, so a redundant re-admit is a benign no-op). - `UnilateralExitState` is **non-terminal** and observed, not fire-and-forget. The actor survives until the unroll job reports a terminal outcome via the manager's `ExitOutcomeNotification`: `ExitOutcomeRecoverable` (the unroll failed with no on-chain footprint) drives `ExitFailedEvent` → `LiveState` + `VTXOStatusUpdate{Live}`, while `ExitOutcomeConfirmed` (the exit confirmed on-chain) drives `ExitConfirmedEvent` → terminal `SpentState` + `VTXOTerminatedNotification` (the actor is reaped here, gated on a terminal on-chain event rather than the user's intent). A recoverable failure of a **recovery-only** target (`ExitOutcomeNotification.ExitPolicyKind.Valid()`, e.g. a vHTLC refund) is the exception: the manager holds the coin in `UnilateralExitState` rather than reliving it, since it is a swap-contract output, not spendable wallet liquidity, and reliving it would inflate balance and feed coin selection and sweep-all. This guard short-circuits before any store access; the owning recovery subsystem is responsible for retrying or terminal-failing the refund. When the actor is absent (e.g. a daemon restart, since exiting VTXOs are excluded from `ListLiveVTXOs` recovery) the manager re-materializes a live actor from the persisted descriptor (recover) or persists `VTXOStatusSpent` directly (confirm). - `Manager.handleForceUnroll` uses `Ask` (not `Tell`) so FSM errors and self-loop no-ops surface as structured `ForceUnrollResponse{Accepted, Reason}` instead of a uniform `Accepted:true` that masks work that was never scheduled. -- When `handleForceUnroll` targets an outpoint with no live actor, `spawnForceUnrollActor` re-materializes an actor from the persisted descriptor so the manager still owns the exit rather than letting the caller admit the unroll behind its back. This is the common shape for the vHTLC recovery target (materialized directly in the store, never admitted through the manager) and any exiting VTXO a restart left out of the live-recovery set. It guards both ends: a missing descriptor returns `ForceUnrollResponse{Accepted: false, Reason: "no such vtxo"}`, and an already-terminal descriptor (`statusToState(...).IsTerminal()`) returns `Reason: "already terminal"` — neither spawns an actor that would immediately reap itself. +- When `handleForceUnroll` targets an outpoint with no live actor, `spawnForceUnrollActor` re-materializes an actor from the persisted descriptor so the manager still owns the exit rather than letting the caller admit the unroll behind its back. This is the common shape for the vHTLC recovery target (materialized directly in the store, never admitted through the manager) and any exiting VTXO a restart left out of the live-recovery set. It guards both ends: a `Store.GetVTXO` miss — signaled via `ErrVTXONotFound` or a nil descriptor, both treated identically — returns `ForceUnrollResponse{Accepted: false, Reason: "no such vtxo"}`, and an already-terminal descriptor (`statusToState(...).IsTerminal()`) returns `Reason: "already terminal"` — neither spawns an actor that would immediately reap itself. Any other `GetVTXO` error surfaces as a hard `"load vtxo for force-unroll"` failure, not a decline. - Admission types (`SelectAndReserveSpendRequest`, `SelectAndReserveForfeitRequest`, `ReserveForfeitRequest`, etc.) are defined in `lib/actormsg` and re-exported as type aliases to avoid wallet → vtxo → round → wallet import cycles. - `selectAndReserveVTXOs` is a shared helper parameterized by `reserveParams` that serves both the OOR spend and cooperative forfeit coin selection paths, avoiding code duplication. - `IncomingVTXOHandler` only handles `VTXO_EVENT_TYPE_CREATED` events. Other event kinds, missing/short outpoints, empty pkScripts, oversized values (`> int64` or `> MaxSatoshi`), and tapscript derivation failures all return success without persisting — they cannot crash the actor or block the indexer push stream. Real DB lookup/save errors are surfaced.