738 lines
45 KiB
Markdown
738 lines
45 KiB
Markdown
# Paid content: recovery before response headers
|
||
|
||
Status: OPEN, identified during IndeeHub rental integration on 2026-10-06.
|
||
This is a source-confirmed gap. No new real-money failure was induced.
|
||
|
||
## Confirmed boundaries
|
||
|
||
`api/rpc/content.rs::handle_content_download_peer_paid` checks the existing
|
||
purchase index and FIPS route before calling a wallet. It persists ownership
|
||
through `content_owned::record_purchase_stream` only after successful response
|
||
headers. Thus the previously qualified interrupted cached-body/seek cases do
|
||
not establish recovery during wallet preparation or after seller settlement but
|
||
before headers reach the buyer.
|
||
|
||
Cashu `send_token_at` may swap inputs remotely before saving the local wallet
|
||
and returning the prepared token. Its deterministic output derivation supports
|
||
wallet restoration, but is not a correlated purchase operation journal.
|
||
Concurrent mutations also need an operation-wide wallet reservation/commit
|
||
boundary, beyond the existing atomic file writer. Fedimint operation IDs and
|
||
out-of-band note recovery must be handled using that backend's actual semantics.
|
||
|
||
The seller currently redeems a presented token in `content_server::serve_content`
|
||
after preparing readable media. It does not persist a Cashu purchase receipt
|
||
that can authorize subsequent delivery without attempting redemption again.
|
||
A token hash alone is not evidence that redemption settled.
|
||
|
||
## Immediate correction being qualified
|
||
|
||
Choose the ecash backend before either wallet operation begins. An explicit
|
||
choice remains pinned; automatic selection reads the home-mint spendable Cashu
|
||
balance and selects Fedimint only when that balance is insufficient. Never fall
|
||
through to another wallet after an attempted operation returns an error: the
|
||
remote mint may already have consumed inputs. Reject unknown method names.
|
||
This prevents a second backend operation in the same RPC; it does **not** solve
|
||
crash recovery or make a fresh user retry safe.
|
||
|
||
## Required implementation and acceptance
|
||
|
||
1. Persist an unpredictable purchase ID and immutable seller/buyer/content/hash,
|
||
terms, amount, method and protocol capability before any mint/spend. A damaged
|
||
or ambiguous journal must block a second payment, preserving original data.
|
||
2. Journal wallet input reservations and recoverable output derivation/operation
|
||
IDs before a remote mutation. Commit wallet change, prepared token and operation
|
||
result durably. Serialize all competing wallet mutations, including receives,
|
||
melts and restores, without deadlocking nested operations.
|
||
3. Persist the seller's receipt/settlement transition. Recover ambiguous receipt
|
||
writes through correlated wallet operation results, not balance changes or
|
||
acceptance of the client's claimed outcome. Repeated delivery requests for
|
||
the same settled purchase must not redeem/pay again.
|
||
4. Bind delivery authorization to authenticated buyer and immutable content/terms,
|
||
with an unpredictable capability. Never turn a public on-chain address into
|
||
a bearer authorization credential. Negotiate updated peer capability; do not
|
||
assume old nodes implement this receipt protocol.
|
||
5. Retain prepared tokens privately until delivery or confirmed refund settles.
|
||
Record actual refunded amounts/fees. A failed refund is not proof of payment
|
||
failure and must not clear an ambiguous purchase for another charge.
|
||
6. Exercise interruption at every write/send/receipt boundary, duplicate requests,
|
||
process reconstruction, full restart, corrupt journal, disk-full, changed
|
||
offers, wrong buyer, incompatible peer, and mint/federation rejection. Prove
|
||
wallet conservation and one settlement with disposable deterministic fixtures
|
||
before bounded live payments. Keep rented cache authorization separate from
|
||
permanent paid-file ownership.
|
||
|
||
The existing two-node 1-sat purchases and cached-delivery tests remain valid for
|
||
their documented scope. They must not be relabeled as acceptance of these open
|
||
initial-payment/recovery requirements. No additional real payment is needed to
|
||
prove the immediate backend-selection regression.
|
||
|
||
## Backend-selection qualification
|
||
|
||
The immediate correction passes **12 focused content RPC tests**, including
|
||
injected ambiguous Cashu/Fedimint failures proving the unselected wallet future
|
||
is never polled, explicit-choice preservation and unknown-method rejection.
|
||
The full isolated suite passes **1,749 tests, zero failures, five existing
|
||
ignored tests**. Logs: `/tmp/archy-peer-payment-selection-tests.log` and
|
||
`/tmp/archy-peer-payment-selection-full-tests.log`. Only the required isolated
|
||
runner was used. No live wallet data or real payments were involved. This is
|
||
source/test qualification; production build and deployment are separate.
|
||
|
||
## Recovery-metadata prerequisite qualified (2026-10-06)
|
||
|
||
Seed reads now distinguish genuine absence from I/O failure. Loading a damaged
|
||
recovery source returns an error instead of silently enabling random outputs.
|
||
A configured recovery source must reserve/derive outputs successfully before a
|
||
mint request; derivation/counter failures no longer fall back to random secrets.
|
||
Legacy wallets which genuinely have no seed retain their existing output path.
|
||
|
||
Counter reservations reject empty/corrupt/unreadable existing files rather than
|
||
resetting to zero. Updates use an owner-only sibling temporary file, file flush,
|
||
atomic replacement and directory flush before returning a usable reservation.
|
||
Invalid derivation is rejected before reserving counters. Existing counter
|
||
serialization within the management process is preserved; this is not a claim
|
||
that a new cross-process wallet lock or the purchase journal has been implemented.
|
||
|
||
Six new regressions cover damaged seed/counter reads, preserved damaged files,
|
||
concurrent reservations/reload/private permissions/temp cleanup, invalid keysets,
|
||
no silent random-output fallback and explicit legacy behavior. Wallet-focused
|
||
isolated result:150passed,0failed,1existing ignored. Full isolated result:
|
||
**1,755passed,0failed,5existing ignored**. Evidence:
|
||
`/tmp/archy-wallet-recovery-prerequisites-tests.log` and
|
||
`/tmp/archy-wallet-recovery-prerequisites-full-tests.log`.
|
||
|
||
Read-only checks found well-shaped seed/counter JSON on dev and Yaya; no secret
|
||
values were printed and no wallet files were changed by those checks. Production
|
||
build/deployment of this prerequisite remains pending. Durable initial purchase
|
||
intent, mint-operation recovery, seller receipt and refund recovery remain open.
|
||
|
||
Strict-schema follow-up: an existing counter document must actually contain its
|
||
counter map. Empty JSON objects and null maps are rejected without modification,
|
||
not deserialized as a fresh zero state. The full isolated suite again passes
|
||
1,755tests,0failures,5existing ignored in
|
||
`/tmp/archy-wallet-recovery-strict-schema-full-tests.log`. No live deployment or
|
||
claim of complete initial-payment recovery is implied.
|
||
|
||
## Additional wallet boundaries found during review (6 October)
|
||
|
||
Source review of `ecash::melt_tokens`, `swap_between_mints` and
|
||
`MintClient::melt_tokens` found further work required before full recovery can
|
||
be accepted. The melt path does not currently submit or retain fee-change
|
||
outputs, and the caller does not require a PAID response before proceeding.
|
||
Cross-mint recovery records are written after the remote operation, leaving an
|
||
interruption window. These are source findings, not newly induced live losses.
|
||
|
||
The implementation must account for NUT-05 quote states and NUT-08 change using
|
||
the mint's advertised support, preserve uncertain operations, and verify amount
|
||
conservation. Reference specifications:
|
||
https://github.com/cashubtc/nuts/blob/main/05.md and
|
||
https://github.com/cashubtc/nuts/blob/main/08.md .
|
||
|
||
Wallet-wide serialization must also include network changes and streaming
|
||
revenue writes; a load/save outside the operation lock can overwrite another
|
||
mutation. Current empty-wallet-file handling, file permissions and directory
|
||
fsync require review as part of durable storage. Counter fail-closed tests do
|
||
not establish that this larger transaction journal has been implemented.
|
||
|
||
### Wallet storage durability follow-up
|
||
|
||
Existing empty/whitespace wallet files now fail closed instead of reporting zero;
|
||
only a missing file creates fresh state. Atomic saves use unique0600 temporary
|
||
files, file and directory fsync, and cleanup on failure. Concurrency tests prove
|
||
whole-file replacement (not read-modify-write serialization), private permissions
|
||
and retained targets on rename failure. Wallet tests:57passed. Full backend
|
||
qualification passes in `/tmp/archy-wallet-storage-tls-full-tests.log`. No live
|
||
wallet was altered. Operation serialization and recovery journal remain open.
|
||
|
||
### Serialized wallet mutations and seed durability
|
||
|
||
The wallet now serializes public mutations by canonical node data directory,
|
||
including network changes and seed establishment/import. Independent node
|
||
fixtures retain separate locks; nested send/swap paths use private implementations
|
||
under the outer lock. Streaming revenue records use the same boundary, and direct
|
||
wallet saves are restricted to the wallet module. This is in-process serialization,
|
||
not a cross-process transaction journal or a claim that an entire payment RPC is
|
||
recoverable.
|
||
|
||
Damaged network configuration now errors instead of silently choosing mainnet;
|
||
only an absent configuration retains the historical mainnet default. The previous
|
||
seed is durably copied to a unique private backup before atomic replacement,
|
||
rather than moved away before the replacement write succeeds.
|
||
|
||
Final isolated suite: **1,764 tests pass, zero failures, five existing skips**.
|
||
Log: /tmp/archy-wallet-mutation-seed-final-tests.log. Regressions include eight
|
||
simultaneous real HTTP/curve-signed fixture receipts plus sixteen history writes,
|
||
preserving255sats and24entries; canonical/symlink lock identity; damaged network
|
||
configuration; and simultaneous seed establishment/unique retained backups.
|
||
No live wallet data or additional real payments were used.
|
||
|
||
Still required: correlated purchase and mint-operation journal, recoverable
|
||
prepared outputs, quote/change handling and seller receipts, interrupted-operation
|
||
recovery and complete timed-playback integration. Higher-level Minibits claim and
|
||
purchase flows must pin their network/terms across their entire business operation;
|
||
serializing individual wallet calls alone does not provide that contract.
|
||
|
||
### Recoverable prepared Cashu swaps
|
||
|
||
MintClient now separates preparation from execution. Prepared requests contain
|
||
the exact outputs and private unblinding material, can survive serialization, and
|
||
validate their mint, commitments, values and keyset before execution. Debug output
|
||
omits bearer secrets. Recovery uses the original outputs, matches returned curve
|
||
points independently of response ordering/hex case, and rejects partial,
|
||
duplicate, unknown or mismatched signatures. An empty restore response remains
|
||
uncertain; it is never interpreted as permission to spend again.
|
||
|
||
Real HTTP/curve fixtures simulate a mint consuming inputs and returning500instead
|
||
of its successful response. A reconstructed client recovers the original valid
|
||
proofs without a second swap. Negative tests cover changed preparation and
|
||
malformed recovery. The first run failed an overly strict test comparing JSON
|
||
bytes with unordered map keys; the corrected test compares semantic contents.
|
||
Original log: /tmp/archy-prepared-swap-tests.log. Final complete isolated backend
|
||
suite: **1,767 pass, zero failures, five existing skips**, in
|
||
/tmp/archy-prepared-swap-final-full-tests.log.
|
||
|
||
This introduces the request/recovery primitive. Existing swap callers still
|
||
execute immediately; durable wallet reservations and the correlated purchase
|
||
journal are not wired yet. No live money, wallet state or app deployment changed.
|
||
|
||
### Operation journal qualification in progress
|
||
|
||
A separate private write-ahead store now records immutable operation ID, network,
|
||
mint, amount and purchase-context hash alongside the exact request material.
|
||
Records advance from prepared to saved result to committed; they cannot skip the
|
||
saved-result boundary. Retries retain the original request, changed terms are
|
||
rejected, and damaged/unsupported/oversized records block a fresh operation.
|
||
Files use0600, the journal directory0700, atomic replacement and file/directory
|
||
flushes. A wallet mutation guard scopes writes to the canonical node directory.
|
||
The checksum detects accidental corruption; it is not authorization against a
|
||
process able to edit the node's private state.
|
||
|
||
The initial full isolated run passed1,773tests with zero failures and five existing
|
||
skips (`/tmp/archy-send-journal-full-tests.log`). Review subsequently added explicit
|
||
sat-unit validation and a negative regression. The final full isolated run also
|
||
passed1,773tests, zero failures and five existing skips
|
||
(`/tmp/archy-send-journal-final-tests.log`).
|
||
This storage module is not yet connected to wallet reservation/commit or paid-file
|
||
purchase/receipt handling and has not been deployed. Do not infer complete
|
||
payment recovery from the storage tests.
|
||
|
||
The next integration must reserve selected inputs with an operation owner before
|
||
any remote request, recover the exact prepared outputs, and commit change/history
|
||
once. A restored result must match all outputs; absence is not proof of failure.
|
||
An input-state response must account for every requested proof without foreign or
|
||
duplicate entries. Pending/spent/unknown states must never authorize a new payment.
|
||
See the current [NUT-07](https://github.com/cashubtc/nuts/blob/main/07.md) and
|
||
[NUT-09](https://github.com/cashubtc/nuts/blob/main/09.md) specifications. Wallet
|
||
integration still needs crash-boundary fixtures, followed by purchase-context and
|
||
seller-receipt integration before any new paid-content acceptance claim.
|
||
|
||
### Additional restore checks retained for the next batch
|
||
|
||
Review of seed restore found that the current NUT-07 consumer verifies response
|
||
length but not each returned proof identifier. Match every response to the exact
|
||
requested curve point and reject unknown state values before crediting proofs.
|
||
Do not treat a same-length response as sufficient. Also prevent a seed scan from
|
||
making reserved outgoing operation outputs available before that operation's
|
||
result/commit is recovered. These are source findings; no live restore was run.
|
||
|
||
### Wallet reservation/commit boundaries qualified
|
||
|
||
The journal can now durably reserve its exact inputs with an operation owner,
|
||
refuse another operation's reservation, and commit its saved token/change/history
|
||
once. Recovery after purse-save but before journal-phase-save uses the same stable
|
||
transaction ID. Changing the selected network cannot redirect that commit into
|
||
the other purse. Outgoing swap proofs are retained as locally spent, alongside
|
||
spendable change, so they are not immediately rediscovered as wallet funds.
|
||
|
||
Four additional regressions cover local restart boundaries, competing operations,
|
||
network switching and a real HTTP/curve-signature lost-response scenario. The
|
||
latter reconstructs the journal, restores the original swap outputs, commits twice,
|
||
and verifies one mint swap, one history entry,4sats sent and4sats change from8.
|
||
Full isolated qualification:1,777passed, zero failures, five existing skips,
|
||
`/tmp/archy-journal-wallet-commit-tests.log`. No real sats or live wallet data used.
|
||
|
||
These methods are not yet wired into the purchase RPC or a remote-operation
|
||
executor. Seller receipts, delivery capabilities, ambiguous refunds, melt/change
|
||
and seed-restore interaction remain open. A passing commit primitive is not full
|
||
paid-content recovery acceptance.
|
||
|
||
### Seed-restore interaction qualified
|
||
|
||
NUT-07 responses now require one correctly identified state per requested proof
|
||
in protocol order, with only defined states accepted. Duplicate requests, foreign
|
||
or duplicated response identifiers, omissions, reordered entries and unknown
|
||
states fail closed. Hexadecimal case differences remain accepted.
|
||
|
||
Seed restore inspects the private send journal before contacting the mint: it
|
||
blocks unresolved operations for the selected network/mint and excludes committed
|
||
outgoing token secrets even after legacy purse pruning/removal. Damaged journal
|
||
records block restoration rather than disappearing from the decision. Other
|
||
network/mint operations do not block an unrelated valid restore. Prepared swap
|
||
requests also must cover the bound payment amount before being recorded.
|
||
|
||
Full isolated qualification:1,779passed, zero failures, five existing skips,
|
||
`/tmp/archy-journal-restore-boundary-tests.log`. These source changes remain
|
||
undeployed. The higher-level purchase/receipt executor remains open.
|
||
|
||
### Recoverable send executor qualified locally
|
||
|
||
The caller-owned operation now reserves inputs before remote spending, restores
|
||
the original saved swap after an ambiguous response, and commits its original
|
||
token/change/history once. Changed payment terms reject reuse. A spent input with
|
||
no recoverable output blocks another swap. Exact sends support compact v2 wire
|
||
keyset IDs while retaining the full wallet ID. Seed restoration canonicalizes
|
||
curve points, rejects foreign or duplicate metadata, and requires durable counter
|
||
advancement before crediting recovered funds.
|
||
|
||
The first test run exposed compact-ID comparison and uppercase restore-point
|
||
matching defects; both were fixed. The final isolated run passed1,783tests with
|
||
zero failures and five existing skips:
|
||
`/tmp/archy-recoverable-send-executor-final-tests.log`.
|
||
No real payment or deployment was performed. Purchase RPC integration, durable
|
||
seller settlement/receipt recovery and the end-to-end acceptance remain open.
|
||
|
||
Recovery preflight now rejects malformed or unsupported restore responses before
|
||
reserving or spending inputs, and refuses already-issued newly derived outputs.
|
||
Required restore fields cannot silently default to empty. The malformed-response
|
||
regression verifies unchanged spendable balance/no swap, then successful retry
|
||
when the mint responds correctly. Full isolated1,784passed initially; combined
|
||
catalog-route qualification1,785passed, zero failures/five existing skips.
|
||
|
||
### Recoverable incoming settlement — implementation under qualification
|
||
|
||
A separate private receive journal now binds a caller-owned UUID to its original
|
||
network, canonical mint, token hash, agreed minimum net price and purchase-context
|
||
hash. Incoming proofs are claimed outside the spendable purse. Claims use their
|
||
mint and secrets, so another operation cannot redeem a differently encoded token
|
||
or an overlapping subset. The legacy receive entry point also checks these claims.
|
||
|
||
Preparation verifies recovery support and mint fees before any swap. The exact
|
||
prepared outputs are then saved before POST. An ambiguous response resumes the
|
||
same outputs with NUT-09; empty restoration requires every original input to be
|
||
strictly UNSPENT before the identical request can be retried. No automatic refund
|
||
is inferred from a missing response. Mixed-mint and non-sat tokens are outside this
|
||
primitive's deliberately narrow contract.
|
||
|
||
Commit order is Result → Committing → atomic purse save → Committed. Committing
|
||
stores a hash of the exact pre-save purse bytes (absence differs from an empty
|
||
file). The purse contains an independent `received:<UUID>` commitment marker,
|
||
separate from prunable history; it contains no bearer proofs. A retry either finds
|
||
that marker or requires the exact unchanged pre-save purse. A changed purse whose
|
||
marker is missing causes a manual-recovery hold. In particular, an older wallet
|
||
writer dropping markers is not treated as permission to re-credit the receipt.
|
||
Markers must not be compacted with ordinary history. A completed journal receipt
|
||
returns its original net amount even if the wallet/history was subsequently
|
||
pruned; it never reconstructs funds merely because its caller retries.
|
||
|
||
Pending settlement blocks seed restoration for the bound network/mint. Committed
|
||
incoming outputs remain ordinary wallet funds, subject to the existing mint-state
|
||
checks during seed restoration; they are not outgoing-token exclusions.
|
||
|
||
This is a source implementation under test, not a deployed seller receipt or
|
||
purchase protocol. No real payments were made. Purchase authorization, delivery
|
||
capabilities, transport correlation, refunds, and Fedimint/melt remain separate
|
||
open requirements. Test results will be recorded after the isolated runner exits.
|
||
|
||
### Resumed settlement qualification — 6 October
|
||
|
||
The interrupted session's receive journal, executor and four regression tests
|
||
were recovered intact from the existing worktree. The old focused log stopped
|
||
at compilation; it does not establish a test pass. No wallet data was restored,
|
||
replaced or modified to resume this source work.
|
||
|
||
Review also found that `verify_and_receive_payment`, used by the legacy content
|
||
server, needed the same incoming-proof claim check as ordinary `receive_token`.
|
||
It now refuses to redeem another settlement's claimed proofs, including when the
|
||
mint still considers those proofs unspent. An additional HTTP/curve regression
|
||
covers a rejected first POST, legacy redemption refusal, a malformed state reply
|
||
blocking a second POST, and then verified-unspent retry using the exact original
|
||
request. The new regression must pass before qualification is claimed.
|
||
|
||
Next integration boundary remains buyer purchase intent → recoverable sender →
|
||
correlated seller settlement → durable delivery receipt/capability. The current
|
||
`handle_content_download_peer_paid` still calls the legacy sender and records
|
||
ownership after headers; `content_server::verify_payment_token` still calls the
|
||
legacy payment verifier. These call sites are not yet the new purchase protocol.
|
||
Do not deploy these primitives as a claim that pre-header paid-file recovery is
|
||
complete. Keep the original payment/receipt context for retries and do not send
|
||
another payment to recover delivery.
|
||
|
||
Resumption compile checkpoint: `/tmp/archy-resumed-receive-tests.log` finished
|
||
compilation successfully in 10m20s, but the requested `wallet::payment_tests`
|
||
filter matched zero tests (1,794 filtered out). This is compilation evidence only,
|
||
not a focused test pass. The module is `wallet::ecash::payment_tests`. Final-source
|
||
qualification must include the later legacy-claim guard and fifth receive test;
|
||
its build is queued behind coordinated IndeeHub/browser/image checks to avoid
|
||
competing heavy jobs on the development node.
|
||
|
||
#### Next implementation batch: purchase/receipt contract
|
||
|
||
The next source batch should introduce `content_purchase` journals and protocol
|
||
fixtures before switching the live RPC entry points. Bind a versioned UUID,
|
||
verified buyer/seller DIDs, content SHA256/size, immutable terms hash, Cashu
|
||
network/mint, gross token amount and minimum seller net amount. Account for mint
|
||
fees explicitly; sending the displayed net price is not sufficient when the
|
||
seller pays an input fee. `ContentItem` currently has no content hash, so obtain
|
||
a stable readable content snapshot and authenticated offer before spending.
|
||
|
||
The existing `content_auth` v1 signature covers GET/path/range/time, not payment
|
||
headers or request bodies. Add a separately domain-separated signed-body proof
|
||
for purchase requests covering method/path/audience/body hash; do not treat plain
|
||
`X-Federation-DID` or appended unsigned purchase headers as authentication.
|
||
`PeerRequest::send_json` already provides the transport operation.
|
||
|
||
Persist the seller contract before calling recoverable receive with its UUID and
|
||
context hash; persist receipt/capability after settlement. Authenticated status
|
||
lookup by the same buyer must recover a lost receipt without another redemption.
|
||
Buyer intent precedes recoverable send and retains its original token privately;
|
||
retries resume that purchase and retrieve the receipt, rather than refunding or
|
||
creating another payment after an ambiguous response. Reject unsupported protocol
|
||
versions before spending. Cover changed content/terms, wrong buyer, duplicate
|
||
requests, expired offers, interrupted writes and lost settlement/receipt replies
|
||
with deterministic fixtures before any live purchase acceptance.
|
||
|
||
The next batch now exists as the initially unreferenced
|
||
`core/archipelago/src/content_purchase.rs`: strict versioned contracts/context
|
||
hashes, private original buyer tokens, seller settlement and durable random
|
||
receipts, buyer receipt/delivery states, and cross-process journal locking with
|
||
flushed atomic private records. Six temporary-directory tests cover restart,
|
||
exact replay, changed terms/results, expired new offers versus existing recovery,
|
||
foreign receipts, corrupt/nonregular records and invalid state transitions.
|
||
It performs no wallet or network operations. Root integration will declare the
|
||
module for combined qualification; passing tests are still pending. Callers must
|
||
authenticate offer/receipt provenance and discover/reuse an existing purchase
|
||
UUID before generating another one; this primitive does not yet supply that
|
||
business-level lookup or the transport/serving integration.
|
||
|
||
|
||
Review caught a cancellation ordering issue in the asynchronous rename commit
|
||
point. Purchase journals and the existing purse/send/receive writers now perform
|
||
rename plus directory flush synchronously while their guard is held, so cancelled
|
||
async work cannot later overwrite a newer writer. A deterministic purchase test
|
||
pauses after flushing the temporary file but before commit, cancels the writer,
|
||
then verifies that a subsequent token commit survives and the stale temporary
|
||
file is removed. This is a source correction awaiting the combined isolated run.
|
||
|
||
Next RPC/UI batch acceptance must also include node-side discovery of an existing
|
||
pending purchase by authenticated buyer/seller/content before minting a fresh
|
||
UUID. Caller-only UUID retention is insufficient after lost browser/client state.
|
||
The current journal's UUID binding does not yet implement this lookup. The UI
|
||
must show reserved/pending funds separately from spendable funds; a spendable
|
||
balance of zero must not be presented as zero total owned funds while an operation
|
||
still holds them. Neither requirement is satisfied by the wallet primitive tests.
|
||
|
||
Transport integration detail: the new v2 peer proof hashes the exact request body.
|
||
`PeerRequest::send_json` currently serializes independently inside its FIPS/Tor
|
||
helpers. Add a serialize-once POST-bytes transport before wiring purchase routes;
|
||
sign and send those exact bytes, rather than signing a separately serialized JSON
|
||
value then allowing another `.json()` call to choose the wire bytes. Existing
|
||
content dispatch currently routes GET reads only, so POST purchase routes remain
|
||
a separate integration step. Existing v1 GET authentication must remain compatible.
|
||
|
||
### Combined resumed qualification passed — 6 October
|
||
|
||
The full isolated runner passed **1,808 tests, zero failures, five existing
|
||
ignored tests**, with no filter. Compilation took 9m07s; isolated execution took
|
||
15.01s. This includes all five recoverable-receive regressions, six purchase
|
||
journal tests (including cancellation ordering), eleven media-registration tests,
|
||
and the independently coordinated v2 request-authentication coverage. Only
|
||
`scripts/test-backend-isolated.sh` executed backend tests.
|
||
|
||
Evidence: `/tmp/archy-resumed-combined-backend-tests.log`.
|
||
The exact tested coordinated tree was HEAD
|
||
`1c6daab92a4e7c9fa70b81489c355a35163369b5` plus `git diff HEAD --binary`, SHA256
|
||
`e9a8d75a81a966904d0929a1a1869adb892d266ef1b6b59580b7543a6b0ca7a4`, saved privately
|
||
at `/tmp/archy-resumed-combined-tested-source.patch`. New source SHA256 values:
|
||
|
||
- `content_purchase.rs`: `5d597f48c24ab96d4a7ee348ef641cc1f2d98c411574c46f14c579bea9930e84`
|
||
- `wallet/receive_journal.rs`: `43333ec21a6b1f7613078083d8114ef934bd44af69c9e93138e58a419cb919ac`
|
||
- `media_registration.rs`: `45450e99b50eff3d1115c2b9787e7235e9772209151a06fd09d62c47d0bd93a5`
|
||
- Its `fixtures/v1.json`: `8fbfcbc3beb0b4758fadf677c39c688d55a89ed200d8a7cd8741de0da569feb3`
|
||
|
||
All captured source hashes were rechecked unchanged at test completion before
|
||
this evidence update. Machine-readable provenance is in
|
||
`/tmp/archy-resumed-combined-source-provenance.json`. The test suite's isolated
|
||
subprocess case also prints a one-test result; it is not a second full run.
|
||
|
||
No real payment, wallet replacement, deployment or release occurred. The current
|
||
purchase module is a qualified local journal primitive, not a wired purchase RPC,
|
||
authenticated seller offer/receipt transport, or serving capability. Scratch
|
||
acceptance/deadline/executor work in `/tmp/archy-purchase-executor-next` is separate
|
||
and is NOT included in these test results. Explicit seller acceptance before
|
||
buyer spending and retained late-settlement eligibility are the next batch.
|
||
|
||
### New live report: failed Lightning blocks the other payment methods
|
||
|
||
The 6 October operator test reproduced a distinct payment-choice dead end. Yaya's
|
||
outgoing LND record at 23:23:41 UTC is a 2-sat terminal `FAILED` payment with
|
||
`FAILURE_REASON_INSUFFICIENT_BALANCE`. The old backend turned that state into an
|
||
RPC exception; PeerFiles retained its invoice as unresolved and hid ecash forever.
|
||
The generic frontend Lightning helper also treated unknown returned statuses as
|
||
success. Both source paths now distinguish failed, pending and succeeded.
|
||
|
||
The frontend keeps the failed attempt's receipt/history, but permits another
|
||
method only after an explicit returned failure or read-only `lnd.paymentstatus`
|
||
confirmation for that saved hash. It supports old deployed backends that throw
|
||
for terminal failures. An existing saved attempt can be checked without asking
|
||
for another invoice or sending another payment. Unknown, unavailable, possibly
|
||
settled and corrupt saved attempts remain blocked from a second payment; delivery
|
||
recovery remains available. This does not cancel the seller's invoice or claim
|
||
that an externally displayed invoice cannot subsequently be paid.
|
||
|
||
Focused qualification passed 108 tests across PeerFilesLightning and rpc-client:
|
||
`/tmp/archy-payment-switch-ui-tests.log`. Coverage includes returned terminal
|
||
failure, old-backend exceptions, persisted failed-attempt recovery, ambiguous
|
||
status retaining its block, and unknown status never becoming success. The new
|
||
Rust LND status test is awaiting the coordinated combined isolated backend run.
|
||
These changes are not yet claimed deployed or accepted on the three real nodes.
|
||
|
||
Separate live evidence must remain open: Yaya's 100-sat ecash request to Archi
|
||
Dev Box at 23:25:16 UTC encountered an unavailable FIPS route/connection timeout;
|
||
the existing backend logged reclaim at 23:25:33. Investigation did not initiate
|
||
that reclaim or any payment. Dev's FIPS/backend services and listeners were active,
|
||
but the journals show repeated control-socket/seed/peer connection timeouts.
|
||
Framework access was restored and actual hostname `framework-pt` verified. At
|
||
23:24:58 its seller catalog pruned `Photos/web54321-balanced-2.mp4` because its
|
||
backing file was missing; neither supported source path exists. This is separate
|
||
from the closed historical Framework LND-startup incident. Raw node journals and
|
||
sanitized payment summaries are retained privately under
|
||
`/tmp/archy-payment-switch-incident/` (mode 0600).
|
||
|
||
Acceptance still requires real-node no-charge checks of failed-Lightning method
|
||
switching, unknown/settled delivery recovery, reconnect and cross-method state,
|
||
seller missing-file/changed-catalog feedback, and FIPS delivery between Yaya,
|
||
Framework and dev. No new real payment, proof mutation, data deletion, or pending
|
||
state reset was performed during this investigation.
|
||
|
||
Further review separated three remaining cases from that targeted fix. An old
|
||
failed local receipt must not be reused automatically for a newly displayed QR;
|
||
request a fresh seller invoice. Known in-flight Lightning recovery should return
|
||
promptly with retained receipt and a check-later action, instead of locking the
|
||
UI behind a long delivery request. Spending RPCs must use a single network
|
||
attempt: automatic timeout/502 retries of legacy ecash purchase or on-chain send
|
||
can otherwise repeat a mutation. These frontend refinements and two additional
|
||
regressions are prepared; their focused rerun is queued behind backend isolation.
|
||
|
||
The larger payment-flow followup remains required: persist per-item on-chain
|
||
address/amount before broadcast, preserve txid and uncertain send state on close
|
||
and reload, and resume seller/status/cache lookup rather than issue another send.
|
||
Persist ecash dispatch intent and recover through authoritative backend purchase
|
||
state rather than call a potentially spending legacy endpoint as a status check.
|
||
Unpaid/failed/ambiguous/settled states must stay distinct across method changes;
|
||
externally exposed QR invoices/addresses remain payable until their actual expiry
|
||
or cancellation, and local attempt failure is not evidence of their cancellation.
|
||
Browser storage is supplemental only: server-owned pending lookup must survive
|
||
lost client state and another browser. This cannot be called fully fixed by the
|
||
current Lightning-only recovery improvements.
|
||
|
||
Current no-payment route check: Yaya's FIPS request to dev's `/health` timed out
|
||
before TCP connection after four seconds; the same FIPS endpoint on dev returns
|
||
200 locally. The target matches dev's live fips0 address, backend is listening,
|
||
and nft accepts TCP 5679 on fips0. Both mesh daemons report connected common peers,
|
||
but no direct link between those two nodes. Further mesh routing diagnosis is
|
||
required; no firewall or daemon state was changed. Framework's live FileBrowser
|
||
bind mounts point to `filebrowser` and `filebrowser-data`; both were searched for
|
||
the stale filename, with no match. The persistent ext4 mount is present and its
|
||
Photos/Videos directories exist. This establishes absence from the current Cloud
|
||
roots, not deletion everywhere or a justification to rebuild any buyer receipt.
|
||
|
||
The final focused frontend rerun passed **110 tests across two files** in 8.52s
|
||
(`/tmp/archy-payment-switch-ui-tests-final.log`), including the fresh-QR and prompt
|
||
pending-status cases. The combined isolated backend batch passed the LND status
|
||
regression and all purchase-executor tests, but was **not an overall pass**:
|
||
1,821 passed, one failed, five ignored. Its failure is in the separately owned
|
||
FIPS duplicate-identity transport assertion; follow-up is underway. The urgent
|
||
frontend source is frozen for the coordinated production build.
|
||
|
||
Subsequent read-only route checks by the coordinating agent returned Yaya→dev
|
||
health 200 twice (2.57s and 0.49s total), with no restart or configuration change.
|
||
The earlier four-second connect timeout remains valid evidence of an intermittent
|
||
mesh route problem, not a claim of permanent loss of connectivity.
|
||
|
||
### Resumed live qualification and incremental UI deployment
|
||
|
||
The `d94ff097` compatibility fix passed 110 focused frontend tests and the
|
||
production UI build. Yaya now serves UI index SHA256
|
||
`df53b2cae3e8c26c5899fa34a688d1a09dbb250cc7922020301250195cd54a83`.
|
||
Deployment verified the backend binary, session key and every app container's
|
||
identity/start time remained unchanged. Evidence:
|
||
`/tmp/archy-payment-switch-yaya-deploy.log`; rollback:
|
||
`/var/lib/archipelago/support/payment-switch-ui-20261007T000429Z-3702073/rollback.sh`.
|
||
This is the failed-Lightning compatibility fix, not acceptance of every payment
|
||
method or deployment of the new durable purchase backend. No payment was sent by
|
||
these checks.
|
||
|
||
Yaya-to-dev FIPS health probes subsequently recovered without a configuration
|
||
change or restart. Five further requests returned HTTP 200, taking 0.49–1.14
|
||
seconds (`/tmp/archy-payment-route-health-samples.json`). Earlier 4-second
|
||
connection timeouts and the delivery failure remain valid evidence of an
|
||
intermittent path; this does not establish sustained media-route reliability.
|
||
|
||
The next combined isolated backend run passed 1,821 tests with one failure and
|
||
five ignored tests. All 377 captured source/build inputs were unchanged. The
|
||
failure exposed a purchase binding check using a federation display loader that
|
||
deduplicates onion records. The correction reads the original records under the
|
||
store lock and rejects ambiguous bindings or mismatched DID/public-key pairs;
|
||
its rerun is pending. Installer, settlement executor and LND status tests passed
|
||
within this failed combined run; the full batch is not qualified yet.
|
||
|
||
Dev subsequently completed the same UI deployment with matching served index,
|
||
unchanged backend/session/app containers and rollback at
|
||
`/var/lib/archipelago/support/payment-switch-ui-20261007T000454Z-2324163/rollback.sh`.
|
||
Evidence: `/tmp/archy-payment-switch-dev-deploy.log`. Framework is reachable and
|
||
healthy but runs a different backend/UI revision; this incremental deployment
|
||
has not changed it.
|
||
|
||
Read-only real-attempt validation: Yaya's existing `lnd.paymentstatus` RPC returns
|
||
`failed`, `Insufficient channel balance`, and 2 sats for the operator's reported
|
||
Lightning attempt. This verifies the deployed compatibility UI's recovery lookup
|
||
against the actual existing backend, rather than only fixtures. Private evidence:
|
||
`/tmp/archy-payment-switch-incident/yaya-failed-lightning-rpc-result.private.json`.
|
||
The check did not pay, retry a payment, change a receipt, or infer seller invoice
|
||
cancellation. Browser interaction with the operator's own saved attempt remains
|
||
separate from this read-only RPC verification.
|
||
|
||
## Combined qualification after resumed integration
|
||
|
||
The full isolated backend rerun passed 1,848 tests with zero failures and five
|
||
existing skips. All 385 recorded source, build and fixture hashes remained
|
||
unchanged. Evidence: `/tmp/archy-qualified-candidate-backend-rerun-tests.log` and
|
||
`/tmp/archy-qualified-candidate-backend-rerun-provenance.json`. Earlier failed
|
||
runs remain recorded. This qualifies the current backend primitives and Browse
|
||
path repair locally; production build and live deployment checks are next.
|
||
Complete purchase callers, app registration/publication and timed playback
|
||
acceptance remain open. No real payment or public publication was performed.
|
||
|
||
## Qualified backend deployed to all three working nodes
|
||
|
||
Local commit `b52214f7` passed 1,848 isolated backend tests (zero failures, five
|
||
existing skips); all 385 source/build/fixture hashes stayed unchanged. The release
|
||
build completed successfully. Backend SHA256:
|
||
`697f71af4eb1d7160f16ce97bb21d2238a4adf477990888d9877ba943691d1e4`.
|
||
|
||
The same binary is now installed on dev, Yaya and the actual Framework node.
|
||
Each deployment preserved node identity, session key, signed node catalog, UI,
|
||
stopped/uninstalled choices and all app container IDs/start times. Health passed
|
||
on all three; authenticated owner RPC passed on dev/Yaya. Framework's password
|
||
was accepted, but its normal dashboard login still requires the operator's TOTP
|
||
code, so that authenticated dashboard check remains open. Rollback scripts and
|
||
receipts are retained under each node's support directory and locally in
|
||
`~/.local/state/archipelago/release-qualification/backend-b52214f7/`.
|
||
|
||
Yaya now advertises the installed V4V `/browse` route. Real mobile (390px) and
|
||
desktop (1440px) checks passed Browse, native Nostr login, same-frame playback,
|
||
previous/next/shuffle and decoded artwork. Evidence is in the native-player
|
||
follow-up. The native private app image/catalog were not changed.
|
||
|
||
This deployment includes file availability and minimum on-chain amount checks
|
||
and the tested recovery primitives. The complete new purchase caller and
|
||
IndeeHub publication/playback integration remain under development; these are
|
||
not claimed accepted. No new real payment or public publication was performed.
|
||
|
||
## Payment dialog ownership fixes deployed to all three nodes
|
||
|
||
Source `47cd915e` passed 31 focused UI tests and the production typecheck/build;
|
||
all 492 recorded inputs remained unchanged. The identical UI is deployed on dev,
|
||
Yaya and Framework (index SHA256
|
||
`ad690dba7744ae1c70489576a2f9ff91ad73a993533d72107e59933fd24da485`).
|
||
Each rollout preserved the qualified backend, session key and app containers;
|
||
served-index and health checks passed. Per-node rollback scripts are recorded in
|
||
the deployment receipts. Artifacts, test logs and receipts are preserved under
|
||
`~/.local/state/archipelago/release-qualification/ui-47cd915e/`.
|
||
|
||
The changes bind asynchronous results to their original payment screen, guard
|
||
late clipboard/timer callbacks, prevent a new send after navigation before
|
||
dispatch, and preserve an already-dispatched Lightning receipt. The minimum
|
||
on-chain amount matches the backend. These checks do not establish complete
|
||
durable recovery for every payment method. Full purchase integration and live
|
||
acceptance remain open; Framework dashboard verification still needs normal TOTP.
|
||
No new real payment was made.
|
||
|
||
## Next integrated caller batch — qualification in progress
|
||
|
||
The owner Cloud purchase RPC, registered video rental RPC and FIPS seller routes
|
||
are now connected to the durable purchase journal. Fresh spending requires the
|
||
original operation UUID, envelope hash and exact wallet debit returned for owner
|
||
confirmation. Reopening a title checks its existing operation first. Cancellation
|
||
must obtain the seller's unspent acknowledgement before a replacement quote.
|
||
External Lightning QR invoices expose authoritative lifecycle state; a local timer
|
||
alone cannot authorize another payment method.
|
||
|
||
The native IndeeHub rental broker and player are now implemented in source. The
|
||
browser receives a session-bound local playback handle, not the seller receipt
|
||
capability. Handle creation/status do not start the viewing period; the actual
|
||
video GET does. The app uses `preload="none"` and no autoplay. Closing or changing
|
||
content invalidates asynchronous UI results, and status polling cannot initiate
|
||
a purchase. Native origin, app lifecycle and HTTP/HTTPS route review are ongoing.
|
||
|
||
This is **not deployed or accepted**. The combined Rust source is undergoing its
|
||
isolated compile/test run. Focused registration tests passed 54 backend, seven
|
||
app caller and six host bridge tests; the app rental player passed eight mounted
|
||
lifecycle/status cases and frontend typecheck. Host rental tests initially passed
|
||
five cases before cancellation/status additions and further independent review;
|
||
final host qualification remains pending. Two preexisting backend test/mock type
|
||
omissions remain recorded separately from the passing production typecheck.
|
||
|
||
The original Framework paid-file recovery, real Yaya↔Framework/dev method-switch
|
||
acceptance, app image deployment, complete published-title playback and physical
|
||
companion checks remain open. No new real payment or public announcement was made.
|
||
|
||
### Integrated purchase qualification and seller policy correction
|
||
|
||
The integrated caller, immutable offers, durable acceptance/cancellation, fee-plan
|
||
execution, retained Cloud delivery and registered rental plumbing are now in the
|
||
candidate source. These are not yet accepted as a live payment flow. The first
|
||
combined compile failed before tests; subsequent isolated runs reported 1,884
|
||
passed/3 failed/5 ignored, then 1,886 passed/1 failed/5 ignored. The latter run
|
||
verified all 406 captured source inputs unchanged. Logs are retained at
|
||
`/tmp/archy-purchase-integrated-backend-rerun.log` and
|
||
`/tmp/archy-purchase-integrated-backend-final.log`.
|
||
|
||
The remaining caller test exposed missing explicit mint acceptance in its seller
|
||
fixture. Production review also found that a configured default mint could be
|
||
quoted without checking the seller's redemption allowlist. The candidate now
|
||
checks network and mint policy before new offers/acceptance and before reporting
|
||
an unresolved accepted operation ready for fresh buyer spending. Wallet policy
|
||
changes cannot remove a mint or switch networks while an accepted seller
|
||
liability remains unresolved. Acceptance and policy updates use the same wallet
|
||
then purchase lock order. Settlement and cancellation release that policy pin;
|
||
already-settled receipts remain recoverable after policy changes. No generic
|
||
redemption allowlist bypass was added. New tests cover these transitions and the
|
||
original lost acceptance/settlement sequence; this correction awaits the next
|
||
isolated run.
|
||
|
||
Confirmation responses additionally carry the original saved offer's Cashu
|
||
network and mint for display. They are not derived from later wallet settings.
|
||
No new real payments were made for these checks.
|
||
|
||
Remaining protocol work is explicit: authenticated server-owned creation and
|
||
cancellation of external Lightning invoices across browser-state loss; durable
|
||
on-chain address/dispatch/transaction recovery; and explicit rental renewal only
|
||
after seller-authoritative expiry evidence. Existing paid receipts and ambiguous
|
||
legacy attempts must retain recovery access. A local timeout, missing browser
|
||
record or clock expiry must never authorize a second payment.
|
||
|
||
Qualification update: the corrected combined isolated backend run passed **1,889
|
||
tests, zero failures, five existing skips**. Compilation took 4m22s; isolated
|
||
execution took 14.60s. All 406 captured inputs remained unchanged. Evidence:
|
||
`/tmp/archy-purchase-policy-backend-tests.log` and
|
||
`/tmp/archy-purchase-policy-green-provenance.json`. This includes the complete
|
||
caller lost-acceptance/lost-settlement regression, seller policy transitions,
|
||
immutable-content first-use integrity, and explicit application license metadata.
|
||
Production build, deployment and live payment acceptance are separate gates.
|
||
|
||
### Integrated native purchase dashboard qualification — 7 October
|
||
|
||
The matching dashboard passed 1,375 tests across 170 files, the actual project
|
||
TypeScript check, and its production build with 500 source/build inputs unchanged.
|
||
Twenty-one local browser cases passed at 320, 390 and 1440px, including long mint
|
||
URLs, Cashu network labels, busy/error states and reachable mobile actions. These
|
||
are local fixtures, not live payment acceptance. The earlier full-suite loading
|
||
notice timeout is retained in its log; its unchanged-timeout focused rerun and
|
||
the subsequent complete suite passed.
|
||
|
||
The deployable UI and evidence are preserved under
|
||
`~/.local/state/archipelago/release-qualification/ui-purchase-6fd81faf0d05/`.
|
||
Its index SHA256 is `6fd81faf0d057b1c689610ebfbd04193071e282d85e27ec07b34f927525be1f5`.
|
||
It requires the matching purchase backend and is not yet deployed. The prior
|
||
qualified backend and dashboard remain live on dev, Yaya and Framework.
|