diff --git a/docs/Evelyn.md b/docs/Evelyn.md index b9adb5c..e649057 100644 --- a/docs/Evelyn.md +++ b/docs/Evelyn.md @@ -129,6 +129,24 @@ EVE-indbetalinger. Burn kræver stadig en frisk EVE/USDT-observation gennem det uændrede `TickerService.getLatestPrice()` og bekræftes ved `FINALIZED` før det eneste notifikationsforsøg. Inputgrænserne begrænser ikke EVE-burn. +Ved ukendt swap-resultat gemmes den oprindelige signatur, inputvaluta, beløb og +validitetsoplysninger i processen. Senere iterationer undersøger samme signatur +uden at gensende transaktionen. Bekræftet succes eller fejl ved mindst `CONFIRMED` +frigiver nye swaps med frisklæste saldi. Udløb frigiver kun et swap, når den +oprindelige transaktions validitet er udløbet, og frisk finalized historik for +signerens adresse med tilstrækkelig lokal dækning beviser, at den ikke blev udført. +En begrænset historikside, der ikke dækker hele perioden, er fortsat uafklaret. +Jupiter-udfyldte sponsorsignaturer kan afklares ved status, men bruges ikke til at +bevise udløb af den lokalt signerede transaktion. + +Mens et swap er uafklaret, fortsætter eksisterende EVE-burn. Et allerede ventende +burn har førsteprioritet og afklares alene. Fejl bevist før indsendelse tillader et +nyt forsøg i en senere iteration; mulig indsendelse uden troværdig signatur kræver +manuel afklaring. Loggen viser valuta, beløb, signatur, fase/årsagsklasser og næste +handling uden signerdata eller transaktionspayloads. Der findes ingen persistent +journal: genstart mister pending swap/burn og suspensioner, så ukendte +transaktioner skal afklares eksternt før genstart og genoptagelse. + ### 2.3 Finansiering af udvikling Udvikling i Evelyn sker gennem en task-baseret model. Alle opgaver oprettes i IntelliJ YouTrack og bliver individuelt prissat i 'Evelyn IOU'. Når en opgave er funded – enten af Evelyn selv (via overskud) eller af en diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/.openspec.yaml b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/.openspec.yaml new file mode 100644 index 0000000..e3966d7 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-05 diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/design.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/design.md new file mode 100644 index 0000000..1ba4c55 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/design.md @@ -0,0 +1,40 @@ +# Design + +## Context + +See proposal.md and issue #91. The burner currently loses a known timed-out swap's identity behind `swapsSuspended`. Jupiter retains its order's last valid block height internally, but structured outcomes expose no validity metadata and confirmation I/O failures expose a signature only in text. Solana awaiting already uses historical status lookup and a shared five-second gate. + +## Goals / Non-Goals + +**Goals:** Resume buybacks after a safely resolved original transaction while maintaining duplicate prevention, process-local state and the existing burn path. + +**Non-Goals:** Build persistent submission infrastructure or change token amount policy, provider selection, burn confirmation or worker scheduling. + +## Decisions + +1. Keep the shared transaction outcome enum unchanged. Add a small separate reconciliation result and original transaction-validity value in the Solana package, and a read-only reconciliation method. The cached decorator delegates it directly. An additive conservative default permits existing blockchain implementations to await but never infer expiry. +2. Extend the existing structured Jupiter outcome exception with optional validity metadata while preserving the three-argument constructor used by perps. Add typed IOException and InterruptedException subclasses carrying submission-stage certainty, optional trustworthy signature, original validity and the original cause. Preparation failures are explicitly pre-submission; ambiguous execution failures without a signature retain manual suspension. A known confirmation signature is retained even when confirmation I/O or interruption fails. +3. Capture a finalized pre-submission slot, taker address and the order's last valid block height. Extract the original recent blockhash from the serialized transaction message and reject a signer response that changes it. Bind the execution signature to the signed transaction's first signature when it is populated. A sponsor signature completed by Jupiter retains provider identity for status checks, but cannot prove expiry of the local payload. No unrelated latest blockhash participates in expiry evidence. +4. Reconcile in a bounded thirty-second operation using the existing shared RPC gate/deadline. A historical status at CONFIRMED or stronger resolves success/failure. Any lower-commitment observation remains pending. For an absent signature, require finalized epoch height above the original validity height, the original blockhash to be invalid at that finalized context, local `minimumLedgerSlot` at/before the original submission anchor, and a subsequent finalized signer-address history lookup with `minContextSlot` at/after that finalized context and no original signature. One page (limit 1000) must extend strictly before the submission anchor or exhaust the covered retained history; a full page ending at/after the anchor remains unresolved. Missing, pruned, stale, inconsistent or failed evidence remains unresolved. This trusts the configured RPC's retained-history contract; it does not treat a remote archive's earliest block as contiguous local coverage. +5. Pending burns retain absolute priority. Otherwise pending swaps are reconciled before input selection; resolution clears state and selection reloads wallet balances. Unresolved swaps block only new swaps and still reach the existing complete-EVE burn path. Never call `swap()` as reconciliation or resubmit/re-sign an unresolved operation. +6. Log canonical symbol/UUID, attempted amount, known signature, status and next action. Log safe phase/cause classes instead of transport body, signer payload, transaction bytes or arbitrary cause messages. + +## Risks / Trade-offs + +- History pruned before the retained anchor or incomplete/contradictory evidence → keep pending and diagnose the missing evidence; no speculative recovery. +- A process restart discards pending state → document external reconciliation before restart and retain no persistent journal. +- Additional read-only preparation/reconciliation calls add gate latency → use shared pacing and a bounded reconciliation deadline; add no immediate retry loop. +- RPC evidence cannot be independently audited against the entire chain → rely only on the configured history-enabled RPC's local-retention contract and preserve pending on detected uncertainty. + +## Primary references + +- [Solana historical signature status](https://solana.com/docs/rpc/http/getsignaturestatuses). +- [Solana transaction retry guidance](https://solana.com/developers/cookbook/transactions/retry). +- [minimumLedgerSlot](https://solana.com/docs/rpc/http/minimumledgerslot) identifies the lowest retained local ledger slot, which may advance as history is purged. +- [isBlockhashValid](https://solana.com/docs/rpc/http/isblockhashvalid) supports commitment and minimum context slot. +- [Finalized address history](https://solana.com/docs/rpc/http/getsignaturesforaddress) binds absence to a minimum finalized context; Agave rejects a finalized request if its highest super-majority root has not reached that context. +- [Agave RPC implementation](https://github.com/anza-xyz/agave/blob/master/rpc/src/rpc.rs) checks transaction-history availability for historical signature lookup; getFirstAvailableBlock may include BigTable and is not used as local coverage evidence. + +## Migration Plan + +Deployment remains separate. Replace the application only after reconciling unknown transactions in any old process; restarting either version loses in-memory pending state. No configuration or history migration is required, and current reserves/maxima remain unchanged. diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/proposal.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/proposal.md new file mode 100644 index 0000000..3aca927 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/proposal.md @@ -0,0 +1,30 @@ +# Proposal + +## Why + +Issue #91 addresses known Jupiter swaps that time out and currently suspend Evelyn buybacks until a Hub restart. Retaining and reconciling the original transaction safely lets normal hourly buybacks resume after its outcome is resolved, without risking duplicate economic operations. + +## What Changes + +- Retain process-local pending swap signature, input, attempted amount and original validity metadata after an unknown outcome. +- Reconcile that same signature read-only in later iterations at CONFIRMED or stronger; resume selection only after success, on-chain failure or demonstrated non-executed expiry, using freshly read balances. +- Bind original validity to the actual signed transaction identity where available; sponsor-completed identity can resolve status but cannot prove local-payload expiry. +- Preserve known signatures and original causes in structured confirmation errors; classify proven preparation failures as retryable and retain manual handling for uncertain failures without a trustworthy signature. +- Preserve pending-burn priority, existing EVE burns while a swap remains unresolved, one new swap per iteration, all current reserves/maxima and Ticker behavior. +- Add safe operational diagnostics and focused offline verification. + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `evelyn-iou-burner-service`: Later-iteration reconciliation and safe recovery of pending swaps. +- `jupiter-swap-service`: Structured submission stage/signature/cause and original validity context. +- `solana-transaction-awaiting`: Small read-only reconciliation support with conservative expiry/history evidence. + +## Impact + +The burner, Jupiter swap integration, shared structured Jupiter outcome exception, Solana API/reference implementation/cached delegate, and directly affected documentation/specifications are involved. Existing outcome statuses and perps callers remain compatible. No persistent journal, rebroadcast, automatic retry/re-signing, provider routing, live transaction, deployment, #92 minimum configuration, ValueTag change or unrelated burn/lifecycle work is included. diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/evelyn-iou-burner-service/spec.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/evelyn-iou-burner-service/spec.md new file mode 100644 index 0000000..02e843b --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/evelyn-iou-burner-service/spec.md @@ -0,0 +1,69 @@ +# Spec Delta + +## MODIFIED Requirements + +### Requirement: Immediate sequential fixed-delay lifecycle +Each reference implementation instance SHALL own at most one dedicated background worker. A successful Hub-owned `NenjimProcess.start()` SHALL begin the first iteration immediately; every later iteration SHALL begin only after the previous iteration completes and the configured fixed delay elapses; and iterations SHALL never overlap. A repeated process start while active SHALL NOT create another worker. If scheduling the worker fails during startup, the implementation SHALL stop the newly created executor, clear its worker reference and process-local state, and report startup failure. + +Pending swap/burn information and manual swap/burn suspension flags SHALL be process-local only. A complete process restart SHALL clear those states. The public JavaDoc SHALL disclose that restart loses reconciliation state and can weaken duplicate prevention until unknown transactions have been reconciled externally. Neither the service interface nor the implementation SHALL expose ordinary stop or restart operations. + +#### Scenario: First and later iterations +- **WHEN** the Hub starts the Burner process and one iteration takes longer than usual +- **THEN** the first iteration starts without initial delay and the next starts only after completion plus the configured interval + +#### Scenario: Active service is started again +- **WHEN** `NenjimProcess.start()` is called while the instance already owns an active worker +- **THEN** the call is rejected without creating an overlapping worker + +#### Scenario: Explicit lifecycle reset +- **WHEN** a complete process restart occurs after pending or suspended process-local state has been externally reconciled +- **THEN** the new process begins without that prior in-memory state and reloads wallet and chain state normally + +### Requirement: At most one confirmed swap attempt per iteration +After selecting a positive exact amount, the service SHALL invoke the existing Jupiter exact-input operation once with the selected CIS-resolved input mint, fixed EVE output mint, selected amount, and configured slippage. Normal return SHALL be treated as already `CONFIRMED` and SHALL NOT trigger a second confirmation wait. Once invoked, no later input currency SHALL be attempted in that iteration regardless of success or failure, and the service SHALL proceed to reload the complete EVE balance whenever processing was not interrupted. + +A structured definitive on-chain swap failure SHALL be logged without suspending later-process swaps. A timed-out outcome or known-signature confirmation communication error SHALL retain process-local pending swap signature, canonical input, attempted amount and original validity metadata for later read-only reconciliation. Proven pre-submission errors SHALL permit a fresh attempt in a later scheduled iteration. An ambiguous post-submission error without a trustworthy signature SHALL retain manual suspension. Interruption SHALL preserve available pending context and propagate normally. No unknown outcome SHALL cause automatic rebuilding, re-signing or resubmission; pending swaps SHALL still permit existing EVE burn evaluation. + +#### Scenario: First swap fails definitively +- **WHEN** the first attempted input returns a definitive on-chain failure +- **THEN** no second input is attempted in that iteration, the EVE balance is still reloaded, and a later iteration may attempt a new swap + +#### Scenario: Swap outcome is unknown +- **WHEN** the attempted swap times out, throws an I/O failure that permits unknown post-submission status, or is interrupted +- **THEN** no equivalent or alternative swap is attempted while its outcome is unresolved; a known signature is checked in later iterations and a signature-less ambiguous submission requires manual reconciliation + +#### Scenario: Swap returns normally +- **WHEN** Jupiter returns a successful result whose transaction already reached `CONFIRMED` +- **THEN** the burner performs no additional confirmation wait and reloads the wallet instead of deriving its burn amount only from the returned output amount + +### Requirement: Production burner accepts USDT and SyrupUSDC +The production Hub SHALL configure USDT first with reserve 0.1 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation settings and burn recovery behavior SHALL be preserved without new Ticker sources or history storage. + +#### Scenario: Production inputs and settings +- **WHEN** the Hub constructs the production burner +- **THEN** it supplies the two exact reserves and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority and retains the other existing production arguments + +## ADDED Requirements + +### Requirement: Pending swaps reconcile before new input selection +After pending-burn priority, a retained swap SHALL be checked read-only by its original signature at CONFIRMED or stronger in a bounded operation. Success, on-chain failure or demonstrated non-executed expiry SHALL clear it and permit newly read balances to drive at most one new swap. Unknown, absent without expiry proof or I/O outcomes SHALL remain pending without a new swap while allowing existing EVE burns. Interruption SHALL propagate with pending state intact. + +#### Scenario: Original swap resolves +- **WHEN** a later check resolves the original signature as successful, definitively failed or proven non-executed and expired +- **THEN** pending swap state is cleared, complete failure details are logged when applicable, and any new selection uses newly read balances + +#### Scenario: Original swap remains unresolved +- **WHEN** a later check times out, lacks trustworthy expiry/history evidence, observes only lower commitment or encounters I/O failure +- **THEN** the same pending signature is retained for the next scheduled iteration, no new swap occurs and the existing EVE burn path still runs + +#### Scenario: Burn remains pending +- **WHEN** both a swap and a burn are pending at iteration start +- **THEN** only the burn signature is awaited and no swap reconciliation or new transaction is performed + +### Requirement: Safe swap recovery diagnostics +Swap diagnostics SHALL identify canonical input symbol/UUID, attempted amount, known signature, submission or reconciliation state, safe cause details and next action. Timeout SHALL be described as unknown. Credentials, arbitrary provider/signer bodies and serialized transaction payloads SHALL NOT be logged. Pending swap state SHALL remain process-local; restart SHALL lose it and require external reconciliation before safely resuming uncertain activity. + +#### Scenario: Unknown outcome is diagnosed +- **WHEN** a swap outcome remains unknown +- **THEN** diagnostics identify whether its original signature will be checked next iteration or manual investigation is required, without exposing secret or payload data + diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/jupiter-swap-service/spec.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/jupiter-swap-service/spec.md new file mode 100644 index 0000000..abcf0ac --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/jupiter-swap-service/spec.md @@ -0,0 +1,59 @@ +# Spec Delta + +## MODIFIED Requirements + +### Requirement: Confirmed execution result validation +The service SHALL first accept a Jupiter execution response only when it has status `Success`, explicitly has result code `0`, contains a non-blank transaction signature, and contains positive valid actual total input and output raw amounts. It SHALL convert each actual amount using the independently resolved precision of its mint. After this provider-response validation, the service SHALL independently await the returned signature on Solana at `CONFIRMED` using its configured confirmation timeout and SHALL return the already validated swap result only for a `SUCCEEDED` outcome. Non-success HTTP responses, malformed responses, expired or rejected swaps, failed or contradictory status/code combinations, missing result codes, blank signatures, and invalid result amounts SHALL remain provider failures and SHALL not be represented as successful swaps. + +#### Scenario: Provider response and on-chain confirmation succeed +- **WHEN** Jupiter reports status `Success`, code `0`, a signature, and valid actual total input and output amounts, and that signature independently reaches `CONFIRMED` with a `SUCCEEDED` outcome +- **THEN** the service returns the signature and those actual amounts in human-readable units + +#### Scenario: Jupiter rejects or fails execution +- **WHEN** Jupiter returns a failed status, a missing or non-zero result code, a contradictory status/code combination, expiration, rejection, non-success HTTP status, malformed body, blank signature, or invalid actual amount +- **THEN** the service reports an I/O failure without treating provider validation as an on-chain confirmation + +#### Scenario: Provider succeeds but transaction fails on-chain +- **WHEN** a fully validated Jupiter execution response contains a signature whose independent `CONFIRMED` outcome is `FAILED` +- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, slot, and complete compact Solana failure details + +#### Scenario: Provider succeeds but confirmation times out +- **WHEN** a fully validated Jupiter execution response contains a signature whose independent `CONFIRMED` outcome is `TIMED_OUT` +- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, original validity metadata and unknown outcome warning and does not automatically resubmit an equivalent transaction + +### Requirement: Post-submission communication and interruption policy +If independent confirmation fails with an I/O error after the signature is known, the service SHALL throw a structured `IOException` preserving the original signature, original transaction-validity metadata, submission uncertainty and underlying cause. Interruption SHALL propagate as `InterruptedException` while preserving known signature/validity context in structured form without another RPC or Jupiter request. Neither condition SHALL trigger rebuilding, re-signing, retrying, or resubmitting a transaction. + +#### Scenario: Confirmation communication fails +- **WHEN** independent confirmation throws an I/O error for a known submitted signature +- **THEN** the service reports the structured signature, original validity metadata and unknown on-chain outcome while preserving the original error and performs no retry or resubmission + +#### Scenario: Confirmation is interrupted +- **WHEN** independent confirmation throws `InterruptedException` +- **THEN** that interruption retains its known signature and validity context, propagates normally and the service performs no subsequent request + +## ADDED Requirements + +### Requirement: Submission certainty and original validity remain structured +The integration SHALL expose submission certainty, known signature, original cause and original validity in structured form. Original validity SHALL retain the order height, actual blockhash, taker and finalized submission anchor. Signing SHALL preserve the hash and required taker signer. Expiry evidence SHALL be bound to a populated local first signature; contradictory provider identity SHALL require manual handling. Existing perps outcome construction SHALL remain compatible. + +#### Scenario: Preparation fails +- **WHEN** mint/order acquisition, validation, signing or pre-submission validity preparation fails before execution submission +- **THEN** the error is structured as pre-submission and no execution or automatic retry occurs + +#### Scenario: Execution response is lost +- **WHEN** execution may have been submitted and no trustworthy signature is available +- **THEN** the error remains structured as uncertain submission and does not authorize automatic recovery or resubmission + +#### Scenario: Provider identity contradicts the signed transaction +- **WHEN** an execution response signature differs from the populated first signature of the signed payload +- **THEN** the result is not treated as confirmed and uncertain submission requires manual reconciliation + +#### Scenario: Sponsor identity is completed by Jupiter +- **WHEN** Jupiter supplies a valid provider signature while the local first signature is blank +- **THEN** the signature can be awaited and reconciled by status, but original local validity cannot establish non-executed expiry + +#### Scenario: Timeout retains original validity +- **WHEN** confirmation of a known submitted signature times out +- **THEN** the exceptional outcome exposes its original validity data without substituting a new transaction or blockhash + diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/solana-transaction-awaiting/spec.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/solana-transaction-awaiting/spec.md new file mode 100644 index 0000000..e60e4e2 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/specs/solana-transaction-awaiting/spec.md @@ -0,0 +1,33 @@ +# Spec Delta + +## ADDED Requirements + +### Requirement: Read-only transaction reconciliation +The blockchain SHALL support bounded reconciliation of an existing signature at CONFIRMED or stronger, retaining normal await behavior and outcome statuses. Reconciliation SHALL distinguish definitive success/failure, demonstrated non-executed expiry and unresolved evidence. It SHALL use the existing shared RPC gate/deadline and historical status lookup without submission, caching, immediate retries or a new pacing mechanism. Unsupported expiry evidence SHALL remain unresolved. + +#### Scenario: Definitive historical status +- **WHEN** the original signature is observed at or above requested commitment +- **THEN** reconciliation returns its successful or failed outcome with slot and complete failure details as applicable + +#### Scenario: Below commitment or interrupted +- **WHEN** the signature is only observed below commitment or the check is interrupted +- **THEN** expiry is not inferred; an unresolved result is returned for lower commitment and interruption propagates normally + +#### Scenario: Cached decorator +- **WHEN** reconciliation is invoked through the cached decorator +- **THEN** it reaches the delegate independently on every call + +### Requirement: Expiry requires original validity and reliable local history absence +Expiry SHALL require signature-bound original validity, finalized height above its last valid height, original blockhash invalidity, retained local history through its submission anchor and fresh finalized signer-history absence. Missing, pruned, stale, lower-commitment or inconsistent evidence SHALL remain unresolved. Absence, elapsed time or blockhash invalidity alone SHALL NOT authorize clearing a pending transaction. + +#### Scenario: Non-executed expiry is established +- **WHEN** all original-validity, finalized-height, original-blockhash, local-history-coverage and finalized signer-history absence checks succeed, with `minContextSlot` at/after the expired finalized context and a bounded page reaching before the submission anchor or exhausting covered retained history +- **THEN** reconciliation identifies a non-executed expired transaction without resubmitting it + +#### Scenario: Evidence is incomplete +- **WHEN** original metadata is missing, history is unavailable/pruned, a lookup fails, a response is inconsistent/stale or the original blockhash is still valid +- **THEN** no expiry result is returned and the diagnostic explains the missing evidence + +#### Scenario: Bounded history page is incomplete +- **WHEN** a full history page ends at or after the submission anchor +- **THEN** the transaction remains unresolved instead of treating partial absence as proof diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/tasks.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/tasks.md new file mode 100644 index 0000000..d47c82a --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/tasks.md @@ -0,0 +1,17 @@ +# Tasks + +## 1. Preserve submitted swap identity + +- [x] 1.1 Add compatible structured validity, I/O and interruption metadata; verify original signature/cause and pre-submission certainty offline. +- [x] 1.2 Retain the actual transaction blockhash, original last-valid height and pre-submission anchor; verify signer hash consistency without live submission. + +## 2. Reconcile pending swaps safely + +- [x] 2.1 Add bounded read-only Solana reconciliation and direct cached delegation; verify confirmed outcomes and conservative finalized-history expiry evidence with controlled RPC responses. +- [x] 2.2 Preserve process-local pending swaps and safe diagnostics in the burner; verify scheduled recovery, unresolved suppression, fresh balances, EVE burn continuation and pending-burn priority offline. + +## 3. Complete the documented change + +- [x] 3.1 Update service documentation and operational restart limitations; review unchanged amount policy, transaction submission and burn recovery behavior. +- [x] 3.2 Compile existing main/test sources, run the temporary focused verification harness, record evidence and validate OpenSpec. +- [x] 3.3 Sync main specifications and archive the completed change after checking implementation and verification evidence. diff --git a/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/verification.md b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/verification.md new file mode 100644 index 0000000..33c832c --- /dev/null +++ b/openspec/changes/archive/2026-10-06-91-reconcile-pending-burner-swaps/verification.md @@ -0,0 +1,24 @@ +# Verification + +Verified on 2026-10-06 with JDK `/usr/local/software/java/jfx-25`. + +## Compilation and offline checks + +- Final parent compilation: `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 7s**; `detagMain` and `compileJava` executed against the final safe order-diagnostic correction and `compileTestJava` was up-to-date. The preceding successful 9s build also executed `compileTestJava`. +- Temporary harness compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue91 /tmp/evelyn-issue91/Issue91Verification.java`. +- `java --enable-preview -cp '/tmp/evelyn-issue91:build/classes/java/main:build/libs/*' Issue91Verification` → **PASS: 102 focused offline assertions**. Every wallet, swap and HTTP dependency is controlled; no live signing, submission or remote RPC occurs. +- The harness's initial deadline fixture omitted the gate's completed-call flag. Correcting that temporary fixture produced the successful run; no implementation change was necessary. An earlier harness compilation preceded the parent build and saw old classes; it is superseded by this final compilation/run. + +Coverage includes later-iteration success/failure/expiry recovery using fresh balances; same-signature bounded CONFIRMED checks; unresolved/I/O/interruption suppression; pre-submission retry and manual signature-less uncertainty; EVE burn continuation, stale-price gate and pending-burn priority; original timeout/signature/validity/cause retention; compatible perps construction; actual legacy/v0 blockhash and required-taker extraction; blank sponsor identity; invalid provider signature rejection; and finalized expiry evidence with pruning, full history pages, minContextSlot, lower commitment, stale/inconsistent evidence and deadline handling. Safe numeric RPC and Jupiter order HTTP/provider codes are retained without provider bodies/error messages. + +## Review and OpenSpec + +- `git diff --check`: passed. +- Strict validation of the active change and all three synced main specifications: passed. All ten delta requirements match the main specs exactly. After archive, `openspec list --json` reports no active changes and `openspec validate --archived --json --report findings` passes all 29 archived changes with no findings. Existing long-requirement informational hints remain. After intentional main-spec synchronization the change validator may note that ADDED requirements already exist; the semantic archive workflow preserves that synchronized state. +- Independent parent review found no remaining material findings; its safety corrections were implemented before the successful final build. +- Static diff review confirms current reserves/maxima, Hub factory, input selection, Ticker behavior, EVE balance/price gate, burn submission/await/notification and fixed-delay scheduling are unchanged. The provider/local signature contradiction branch retains manual handling. The cached decorator delegates reconciliation directly. +- There remains one managed execution call and no automatic resubmission, persistent journal, ValueTag change or issue #92 minimum-input policy. + +## Limits + +The harness is temporary and outside the repository; no permanent automated test was added. Full live Jupiter/provider integration was not exercised. Expiry relies on the configured RPC's reliable retained-history contract and remains pending when original signature binding, history coverage or finalized evidence is insufficient. Sponsor-completed signatures can resolve by status but cannot establish local-payload expiry. Restart loses process-local pending state, requiring external reconciliation before safely resuming unknown activity. No deployment, push or Gitea change was made. diff --git a/openspec/specs/evelyn-iou-burner-service/spec.md b/openspec/specs/evelyn-iou-burner-service/spec.md index baf4055..264697a 100644 --- a/openspec/specs/evelyn-iou-burner-service/spec.md +++ b/openspec/specs/evelyn-iou-burner-service/spec.md @@ -81,7 +81,7 @@ The service SHALL fix the EVE mint as `meveYG2iXYSkgSUn1T1uxcthH1EGMZdRHGgCntXZA ### Requirement: Immediate sequential fixed-delay lifecycle Each reference implementation instance SHALL own at most one dedicated background worker. A successful Hub-owned `NenjimProcess.start()` SHALL begin the first iteration immediately; every later iteration SHALL begin only after the previous iteration completes and the configured fixed delay elapses; and iterations SHALL never overlap. A repeated process start while active SHALL NOT create another worker. If scheduling the worker fails during startup, the implementation SHALL stop the newly created executor, clear its worker reference and process-local state, and report startup failure. -Pending burn information and swap/burn suspension flags SHALL be process-local only. A complete process restart SHALL clear those states. The public JavaDoc SHALL disclose that restart loses reconciliation state and can weaken duplicate prevention until unknown transactions have been reconciled externally. Neither the service interface nor the implementation SHALL expose ordinary stop or restart operations. +Pending swap/burn information and manual swap/burn suspension flags SHALL be process-local only. A complete process restart SHALL clear those states. The public JavaDoc SHALL disclose that restart loses reconciliation state and can weaken duplicate prevention until unknown transactions have been reconciled externally. Neither the service interface nor the implementation SHALL expose ordinary stop or restart operations. #### Scenario: First and later iterations - **WHEN** the Hub starts the Burner process and one iteration takes longer than usual @@ -130,7 +130,7 @@ For each entry the service SHALL subtract its configured reserve and skip non-po ### Requirement: At most one confirmed swap attempt per iteration After selecting a positive exact amount, the service SHALL invoke the existing Jupiter exact-input operation once with the selected CIS-resolved input mint, fixed EVE output mint, selected amount, and configured slippage. Normal return SHALL be treated as already `CONFIRMED` and SHALL NOT trigger a second confirmation wait. Once invoked, no later input currency SHALL be attempted in that iteration regardless of success or failure, and the service SHALL proceed to reload the complete EVE balance whenever processing was not interrupted. -A structured definitive on-chain swap failure SHALL be logged without suspending later-process swaps. A timed-out structured outcome SHALL suspend all later swaps in that process. Because the Jupiter `IOException` and interruption contracts can represent an unknown post-submission result, either SHALL conservatively suspend later swaps. The service SHALL never automatically rebuild, re-sign, retry, or resubmit such a swap; while swaps are suspended, later iterations SHALL continue only their burn path. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation. +A structured definitive on-chain swap failure SHALL be logged without suspending later-process swaps. A timed-out outcome or known-signature confirmation communication error SHALL retain process-local pending swap signature, canonical input, attempted amount and original validity metadata for later read-only reconciliation. Proven pre-submission errors SHALL permit a fresh attempt in a later scheduled iteration. An ambiguous post-submission error without a trustworthy signature SHALL retain manual suspension. Interruption SHALL preserve available pending context and propagate normally. No unknown outcome SHALL cause automatic rebuilding, re-signing or resubmission; pending swaps SHALL still permit existing EVE burn evaluation. #### Scenario: First swap fails definitively - **WHEN** the first attempted input returns a definitive on-chain failure @@ -138,7 +138,7 @@ A structured definitive on-chain swap failure SHALL be logged without suspending #### Scenario: Swap outcome is unknown - **WHEN** the attempted swap times out, throws an I/O failure that permits unknown post-submission status, or is interrupted -- **THEN** no equivalent or alternative swap is attempted and all later swaps remain suspended until external reconciliation and a complete process restart +- **THEN** no equivalent or alternative swap is attempted while its outcome is unresolved; a known signature is checked in later iterations and a signature-less ambiguous submission requires manual reconciliation #### Scenario: Swap returns normally - **WHEN** Jupiter returns a successful result whose transaction already reached `CONFIRMED` @@ -242,8 +242,30 @@ The service SHALL select the lesser of positive `balance - minimumReserve` and t - **THEN** the service enforces the reserve against the observed balance only and does not estimate fees, even though later fees may reduce the final balance or cause execution to fail ### Requirement: Production burner accepts USDT and SyrupUSDC -The production Hub SHALL configure USDT first with reserve 0.1 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation and recovery behavior SHALL be preserved without new Ticker sources or history storage. +The production Hub SHALL configure USDT first with reserve 0.1 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation settings and burn recovery behavior SHALL be preserved without new Ticker sources or history storage. #### Scenario: Production inputs and settings - **WHEN** the Hub constructs the production burner - **THEN** it supplies the two exact reserves and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority and retains the other existing production arguments + +### Requirement: Pending swaps reconcile before new input selection +After pending-burn priority, a retained swap SHALL be checked read-only by its original signature at CONFIRMED or stronger in a bounded operation. Success, on-chain failure or demonstrated non-executed expiry SHALL clear it and permit newly read balances to drive at most one new swap. Unknown, absent without expiry proof or I/O outcomes SHALL remain pending without a new swap while allowing existing EVE burns. Interruption SHALL propagate with pending state intact. + +#### Scenario: Original swap resolves +- **WHEN** a later check resolves the original signature as successful, definitively failed or proven non-executed and expired +- **THEN** pending swap state is cleared, complete failure details are logged when applicable, and any new selection uses newly read balances + +#### Scenario: Original swap remains unresolved +- **WHEN** a later check times out, lacks trustworthy expiry/history evidence, observes only lower commitment or encounters I/O failure +- **THEN** the same pending signature is retained for the next scheduled iteration, no new swap occurs and the existing EVE burn path still runs + +#### Scenario: Burn remains pending +- **WHEN** both a swap and a burn are pending at iteration start +- **THEN** only the burn signature is awaited and no swap reconciliation or new transaction is performed + +### Requirement: Safe swap recovery diagnostics +Swap diagnostics SHALL identify canonical input symbol/UUID, attempted amount, known signature, submission or reconciliation state, safe cause details and next action. Timeout SHALL be described as unknown. Credentials, arbitrary provider/signer bodies and serialized transaction payloads SHALL NOT be logged. Pending swap state SHALL remain process-local; restart SHALL lose it and require external reconciliation before safely resuming uncertain activity. + +#### Scenario: Unknown outcome is diagnosed +- **WHEN** a swap outcome remains unknown +- **THEN** diagnostics identify whether its original signature will be checked next iteration or manual investigation is required, without exposing secret or payload data diff --git a/openspec/specs/jupiter-swap-service/spec.md b/openspec/specs/jupiter-swap-service/spec.md index d594bf3..3adb437 100644 --- a/openspec/specs/jupiter-swap-service/spec.md +++ b/openspec/specs/jupiter-swap-service/spec.md @@ -102,7 +102,7 @@ The service SHALL first accept a Jupiter execution response only when it has sta #### Scenario: Provider succeeds but confirmation times out - **WHEN** a fully validated Jupiter execution response contains a signature whose independent `CONFIRMED` outcome is `TIMED_OUT` -- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, and unknown outcome warning and does not automatically resubmit an equivalent transaction +- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, original validity metadata and unknown outcome warning and does not automatically resubmit an equivalent transaction ### Requirement: Swap confirmation timeout policy The reference service SHALL accept a constructor-injected, non-null, strictly positive confirmation timeout and SHALL preserve its existing constructor with a two-minute default. The configured duration SHALL be passed unchanged to independent confirmation after submission and SHALL begin governing only when awaiting the known signature starts. @@ -116,15 +116,15 @@ The reference service SHALL accept a constructor-injected, non-null, strictly po - **THEN** construction fails before any operation can be submitted ### Requirement: Post-submission communication and interruption policy -If independent confirmation fails with an I/O error after the signature is known, the service SHALL throw an `IOException` whose message identifies the swap operation, includes the signature, and states that the on-chain outcome is unknown, with the original error retained as its cause. Interruption SHALL propagate directly as `InterruptedException` without another RPC or Jupiter request. Neither condition SHALL trigger rebuilding, re-signing, retrying, or resubmitting a transaction. +If independent confirmation fails with an I/O error after the signature is known, the service SHALL throw a structured `IOException` preserving the original signature, original transaction-validity metadata, submission uncertainty and underlying cause. Interruption SHALL propagate as `InterruptedException` while preserving known signature/validity context in structured form without another RPC or Jupiter request. Neither condition SHALL trigger rebuilding, re-signing, retrying, or resubmitting a transaction. #### Scenario: Confirmation communication fails - **WHEN** independent confirmation throws an I/O error for a known submitted signature -- **THEN** the service reports the signature and unknown on-chain outcome while preserving the original error and performs no retry or resubmission +- **THEN** the service reports the structured signature, original validity metadata and unknown on-chain outcome while preserving the original error and performs no retry or resubmission #### Scenario: Confirmation is interrupted - **WHEN** independent confirmation throws `InterruptedException` -- **THEN** that interruption propagates directly and the service performs no subsequent request +- **THEN** that interruption retains its known signature and validity context, propagates normally and the service performs no subsequent request ### Requirement: API and implementation separation The stable service interface and result value SHALL reside in the Jupiter swap API package, while HTTP DTOs, endpoint handling, throttling, and the reference implementation SHALL remain in the reference-implementation package. @@ -132,3 +132,26 @@ The stable service interface and result value SHALL reside in the Jupiter swap A #### Scenario: Downstream API consumer - **WHEN** downstream code depends only on the Jupiter swap API package - **THEN** it can invoke swaps and inspect results without depending on private wire DTOs or reference-implementation mechanics + +### Requirement: Submission certainty and original validity remain structured +The integration SHALL expose submission certainty, known signature, original cause and original validity in structured form. Original validity SHALL retain the order height, actual blockhash, taker and finalized submission anchor. Signing SHALL preserve the hash and required taker signer. Expiry evidence SHALL be bound to a populated local first signature; contradictory provider identity SHALL require manual handling. Existing perps outcome construction SHALL remain compatible. + +#### Scenario: Preparation fails +- **WHEN** mint/order acquisition, validation, signing or pre-submission validity preparation fails before execution submission +- **THEN** the error is structured as pre-submission and no execution or automatic retry occurs + +#### Scenario: Execution response is lost +- **WHEN** execution may have been submitted and no trustworthy signature is available +- **THEN** the error remains structured as uncertain submission and does not authorize automatic recovery or resubmission + +#### Scenario: Provider identity contradicts the signed transaction +- **WHEN** an execution response signature differs from the populated first signature of the signed payload +- **THEN** the result is not treated as confirmed and uncertain submission requires manual reconciliation + +#### Scenario: Sponsor identity is completed by Jupiter +- **WHEN** Jupiter supplies a valid provider signature while the local first signature is blank +- **THEN** the signature can be awaited and reconciled by status, but original local validity cannot establish non-executed expiry + +#### Scenario: Timeout retains original validity +- **WHEN** confirmation of a known submitted signature times out +- **THEN** the exceptional outcome exposes its original validity data without substituting a new transaction or blockhash diff --git a/openspec/specs/solana-transaction-awaiting/spec.md b/openspec/specs/solana-transaction-awaiting/spec.md index 1b4725c..1326d13 100644 --- a/openspec/specs/solana-transaction-awaiting/spec.md +++ b/openspec/specs/solana-transaction-awaiting/spec.md @@ -96,3 +96,33 @@ The cached Solana blockchain decorator SHALL delegate every transaction-await ca #### Scenario: Repeated awaits through cached decorator - **WHEN** callers invoke transaction awaiting multiple times through the cached decorator - **THEN** every invocation reaches the delegate independently + +### Requirement: Read-only transaction reconciliation +The blockchain SHALL support bounded reconciliation of an existing signature at CONFIRMED or stronger, retaining normal await behavior and outcome statuses. Reconciliation SHALL distinguish definitive success/failure, demonstrated non-executed expiry and unresolved evidence. It SHALL use the existing shared RPC gate/deadline and historical status lookup without submission, caching, immediate retries or a new pacing mechanism. Unsupported expiry evidence SHALL remain unresolved. + +#### Scenario: Definitive historical status +- **WHEN** the original signature is observed at or above requested commitment +- **THEN** reconciliation returns its successful or failed outcome with slot and complete failure details as applicable + +#### Scenario: Below commitment or interrupted +- **WHEN** the signature is only observed below commitment or the check is interrupted +- **THEN** expiry is not inferred; an unresolved result is returned for lower commitment and interruption propagates normally + +#### Scenario: Cached decorator +- **WHEN** reconciliation is invoked through the cached decorator +- **THEN** it reaches the delegate independently on every call + +### Requirement: Expiry requires original validity and reliable local history absence +Expiry SHALL require signature-bound original validity, finalized height above its last valid height, original blockhash invalidity, retained local history through its submission anchor and fresh finalized signer-history absence. Missing, pruned, stale, lower-commitment or inconsistent evidence SHALL remain unresolved. Absence, elapsed time or blockhash invalidity alone SHALL NOT authorize clearing a pending transaction. + +#### Scenario: Non-executed expiry is established +- **WHEN** all original-validity, finalized-height, original-blockhash, local-history-coverage and finalized signer-history absence checks succeed, with `minContextSlot` at/after the expired finalized context and a bounded page reaching before the submission anchor or exhausting covered retained history +- **THEN** reconciliation identifies a non-executed expired transaction without resubmitting it + +#### Scenario: Evidence is incomplete +- **WHEN** original metadata is missing, history is unavailable/pruned, a lookup fails, a response is inconsistent/stale or the original blockhash is still valid +- **THEN** no expiry result is returned and the diagnostic explains the missing evidence + +#### Scenario: Bounded history page is incomplete +- **WHEN** a full history page ends at or after the submission anchor +- **THEN** the transaction remains unresolved instead of treating partial absence as proof diff --git a/src/main/tjava/com/fanitas/evelyn/service/burner/EvelynIOUBurnerService.tjava b/src/main/tjava/com/fanitas/evelyn/service/burner/EvelynIOUBurnerService.tjava index 950fd42..17bd6e0 100644 --- a/src/main/tjava/com/fanitas/evelyn/service/burner/EvelynIOUBurnerService.tjava +++ b/src/main/tjava/com/fanitas/evelyn/service/burner/EvelynIOUBurnerService.tjava @@ -29,7 +29,11 @@ import java.util.List; * iteration; definitive failure is logged without notification; success * receives the burn's single notification attempt. No new swap or burn is * submitted in an iteration that begins with a pending burn. - *