Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af26cb9a0 | ||
|
|
805e2e7e6e | ||
|
|
cfb49ed896 | ||
|
|
e71a8daa8d | ||
|
|
2f30712004 | ||
|
|
28640f3203 | ||
|
|
7e36b7ef95 | ||
|
|
df5cc0eda7 | ||
|
|
0ec22d5215 | ||
|
|
7aab307381 | ||
|
|
1ab439b578 | ||
|
|
2dce841cf4 | ||
|
|
cc7003ac94 | ||
|
|
59c5c62170 | ||
|
|
f1f31ab8f0 | ||
|
|
d057b0f3e6 | ||
|
|
e283f0b8f2 | ||
|
|
00a7f13c6f | ||
|
|
49df1ac9d0 | ||
|
|
152137ed87 | ||
|
|
adb5f0235a | ||
|
|
b9369a870f | ||
|
|
9c636bc93d | ||
|
|
035a01c48c | ||
|
|
f1dd5ed9b1 | ||
|
|
b6a6d544b5 | ||
|
|
681b6feef1 | ||
|
|
d3315900b9 |
@@ -39,6 +39,7 @@ This policy currently applies to:
|
|||||||
- `alarmaction_Pushover.conf`
|
- `alarmaction_Pushover.conf`
|
||||||
- `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf`
|
- `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf`
|
||||||
- `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf`
|
- `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf`
|
||||||
|
- `evelyn-liquidity.conf` (optional version 1 liquidity realization/payout configuration)
|
||||||
- `evelyn.conf`
|
- `evelyn.conf`
|
||||||
- `positions.conf`
|
- `positions.conf`
|
||||||
|
|
||||||
@@ -49,6 +50,25 @@ Nenjim Hub reads only `conf/NenjimHub.conf`; copy
|
|||||||
`conf/NenjimHub.conf.example` before first use and edit the ordered process
|
`conf/NenjimHub.conf.example` before first use and edit the ordered process
|
||||||
list for the deployment role. The example is never loaded as runtime input.
|
list for the deployment role. The example is never loaded as runtime input.
|
||||||
|
|
||||||
|
The Evelyn status processes use `evelyn.status.prod` and `evelyn.status.test`.
|
||||||
|
When upgrading an existing deployment, replace `evelyn.service.prod` and
|
||||||
|
`evelyn.service.test` with those IDs in private `conf/NenjimHub.conf`, preserving
|
||||||
|
their order and enabled/commented selections. The instance names `Production`
|
||||||
|
and `Test`, their `data/evelyn/<instance-name>/status.log` files, and history
|
||||||
|
format version 1 remain unchanged.
|
||||||
|
|
||||||
|
`com.r35157.evelyn.service.status.EvelynStatusService` exposes immutable status
|
||||||
|
history and `AssetPrice getReferencePrice(Instant timestamp)`. The reference
|
||||||
|
price is calculated on demand for the supplied instant and carries the canonical
|
||||||
|
EVE/USDT trading pair. It works before startup and while sampling is stopped,
|
||||||
|
without accessing Ticker or history storage. See [Evelyn](docs/Evelyn.md) for the
|
||||||
|
curve and service contract.
|
||||||
|
|
||||||
|
The optional `evelyn.liquidity.prod` process realizes manually funded
|
||||||
|
EVE/SyrupUSDC CLMM positions, exchanges available SyrupUSDC and sends available
|
||||||
|
USDC from its dedicated wallet. Its deployment configuration and signer are
|
||||||
|
loaded only when selected. See [liquidity service deployment and recovery](docs/EvelynLiquidityService.md).
|
||||||
|
|
||||||
## Release log:
|
## Release log:
|
||||||
| Date | Version | Description |
|
| Date | Version | Description |
|
||||||
|:-----------------|:--------------:|----------------------|
|
|:-----------------|:--------------:|----------------------|
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ FORMAT_VERSION=1
|
|||||||
[AUTO_START]
|
[AUTO_START]
|
||||||
nenjim.registry.service
|
nenjim.registry.service
|
||||||
nenjim.journal.service
|
nenjim.journal.service
|
||||||
|
|
||||||
|
# Optional AssetAZ fact journal (separate from the mandatory Nenjim artifact journal).
|
||||||
|
# assetaz.journal.service
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
FORMAT_VERSION=1
|
||||||
|
# Copy to conf/evelyn-liquidity.conf, replace placeholders and explicitly select
|
||||||
|
# evelyn.liquidity.prod in conf/NenjimHub.conf. This example is never a fallback.
|
||||||
|
# External jup signer must control LIQUIDITY_WALLET; keep secrets outside this file.
|
||||||
|
# Fund the dedicated wallet with SOL for transaction fees and token-account rent.
|
||||||
|
LIQUIDITY_WALLET=REPLACE_WITH_DEDICATED_SOLANA_WALLET
|
||||||
|
SIGNER_KEY_NAME=REPLACE_WITH_EXTERNAL_JUP_SIGNER
|
||||||
|
CLMM_POOL=REPLACE_WITH_EXISTING_EVE_SYRUPUSDC_CLMM_POOL
|
||||||
|
HARVEST_DISTANCE_PERCENT=10
|
||||||
|
ITERATION_INTERVAL_SECONDS=300
|
||||||
|
USDC_RECIPIENT=REPLACE_WITH_RECIPIENT_WALLET_OWNER
|
||||||
|
MINIMUM_SYRUPUSDC_RESERVE=0
|
||||||
|
MINIMUM_USDC_RESERVE=0
|
||||||
|
MAXIMUM_SWAP_SLIPPAGE_BPS=100
|
||||||
|
MAXIMUM_WITHDRAWAL_SLIPPAGE_BPS=100
|
||||||
@@ -1,24 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
LOG4J_CONFIG="conf/log4j2.xml"
|
||||||
|
|
||||||
|
if [[ ! -r "$LOG4J_CONFIG" || ! -s "$LOG4J_CONFIG" ]]; then
|
||||||
|
echo "ERROR: Log4j2 configuration file is missing, unreadable, or empty: $PWD/$LOG4J_CONFIG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
jars=(libs/*.jar)
|
jars=(libs/*.jar)
|
||||||
if (( ${#jars[@]} == 0 )); then
|
if (( ${#jars[@]} == 0 )); then
|
||||||
echo "ERROR: No JARs found in libs/" >&2
|
echo "ERROR: No JARs found in $PWD/libs/" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CLASSPATH=$(IFS=:; echo "${jars[*]}")
|
CLASSPATH=$(IFS=:; echo "${jars[*]}")
|
||||||
|
|
||||||
DEBUG_PORT="${DEBUG_PORT:-5005}"
|
DEBUG_PORT="${DEBUG_PORT:-5005}"
|
||||||
DEBUG_SUSPEND="${DEBUG_SUSPEND:-y}"
|
DEBUG_SUSPEND="${DEBUG_SUSPEND:-n}"
|
||||||
|
|
||||||
echo "Starting NenjimHub in DEBUG mode on port ${DEBUG_PORT} (suspend=${DEBUG_SUSPEND})"
|
echo "Starting NenjimHub in DEBUG mode on port ${DEBUG_PORT} (suspend=${DEBUG_SUSPEND})"
|
||||||
|
|
||||||
exec java \
|
exec java \
|
||||||
|
--enable-native-access=javafx.graphics \
|
||||||
--enable-preview \
|
--enable-preview \
|
||||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=*:${DEBUG_PORT}" \
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=*:${DEBUG_PORT}" \
|
||||||
-Dlog4j.configurationFile=conf/log4j2.xml \
|
"-Dlog4j.configurationFile=$LOG4J_CONFIG" \
|
||||||
-cp "$CLASSPATH" \
|
-cp "$CLASSPATH" \
|
||||||
com.r35157.nenjim.hubd.Main
|
com.r35157.nenjim.hubd.Main
|
||||||
|
|||||||
+320
-4
@@ -28,6 +28,7 @@
|
|||||||
* [8. Risici, fail-safes og antifragilitet](#8-risici-fail-safes-og-antifragilitet)
|
* [8. Risici, fail-safes og antifragilitet](#8-risici-fail-safes-og-antifragilitet)
|
||||||
* [9. Udvikling, YouTrack-integration og offentlig gennemsigtighed](#9-udvikling-youtrack-integration-og-offentlig-gennemsigtighed)
|
* [9. Udvikling, YouTrack-integration og offentlig gennemsigtighed](#9-udvikling-youtrack-integration-og-offentlig-gennemsigtighed)
|
||||||
* [10. Fremtidige emner / TODO](#10-fremtidige-emner--todo)
|
* [10. Fremtidige emner / TODO](#10-fremtidige-emner--todo)
|
||||||
|
* [11. Statusservice og referencepris](#11-statusservice-og-referencepris)
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
|
|
||||||
## 1. Introduktion formål og vision
|
## 1. Introduktion formål og vision
|
||||||
@@ -98,10 +99,107 @@ sikre tokenens pris eller muliggøre ny funktionalitet.
|
|||||||
|
|
||||||
### 2.2 Buybacks og burn
|
### 2.2 Buybacks og burn
|
||||||
En af de vigtigste mekanismer i Evelyn er det automatiske tilbagekøb og efterfølgende burn af 'Evelyn IOU' tokens.
|
En af de vigtigste mekanismer i Evelyn er det automatiske tilbagekøb og efterfølgende burn af 'Evelyn IOU' tokens.
|
||||||
En fast procentdel af overskuddet anvendes på at købe tokens på markedet – uanset den aktuelle pris – og brænde
|
En fast procentdel af overskuddet anvendes på at købe tokens på markedet, når
|
||||||
dem permanent. Dette reducerer det samlede udbud og skaber et kontinuerligt deflationært pres, hvilket typisk er
|
Evelyn Price Index er under nul, og brænde dem permanent. Det reducerer udbuddet,
|
||||||
positivt for investorerne. Buyback-strategien sikrer, at investorerne får en god pris for deres tokens, når det
|
når den aktuelle EVE-pris er lavere end Evelyns referencepris.
|
||||||
udnyttes at ikke alle investorer sælger på samme tid, da de har forskellige forventninger til prisen.
|
|
||||||
|
Når et input ellers er klar til køb, læser burneren en frisk kanonisk EVE/USDT-pris
|
||||||
|
fra `TickerService` og kalder den tilsvarende `EvelynStatusService.getReferencePrice()`
|
||||||
|
med det aktuelle beslutningstidspunkt. Køb tillades kun ved
|
||||||
|
`aktuel pris < referencepris`; lighed giver intet køb. Der bruges samme eksisterende
|
||||||
|
maksimale prisalder som til burn-værdiansættelsen, uden nye prisindstillinger.
|
||||||
|
Referencekurven beregnes af status-servicen; hverken den sidst gemte historikværdi
|
||||||
|
eller grafens punkter bruges til købsbeslutningen.
|
||||||
|
|
||||||
|
Ved lighed, højere pris eller manglende, ugyldige, for gamle eller utilgængelige
|
||||||
|
prisdata springes alle nye køb over i iterationen, og årsagen logges. Forløbet
|
||||||
|
fortsætter til EVE-saldo og mulig burn efter de selvstændige burn-regler. En fejl i
|
||||||
|
referenceopslaget blokerer derfor ikke burn, når dens egen friske værdiansættelse
|
||||||
|
og øvrige krav er opfyldt. Priserne vurderes igen ved næste ellers berettigede
|
||||||
|
iteration; et prisskip sætter ingen permanent suspension.
|
||||||
|
|
||||||
|
Med referencepris 15 USDT/EVE tillader 14,99 et ellers berettiget køb; 15,00 og
|
||||||
|
15,01 springer købet over. I alle tilfælde fortsætter EVE-burn-vurderingen.
|
||||||
|
|
||||||
|
Produktionsprocessen `evelyn.iou-burner.prod` accepterer følgende inputs i rækkefølge:
|
||||||
|
|
||||||
|
| Token | Reserve | Minimum swap-input | Maksimum pr. iteration |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| USDT | 0,1 USDT | 0,05 USDT | 0,25 USDT |
|
||||||
|
| SyrupUSDC | 0,095 SyrupUSDC | 0,045 SyrupUSDC | 0,2 SyrupUSDC |
|
||||||
|
|
||||||
|
Hub'en angiver reserver, minimumsbeløb og maksimummer som `MoneyAmount`.
|
||||||
|
Reservelisten bestemmer accepterede valutaer og prioritet; minimumsbeløb og
|
||||||
|
maksimummer matches efter valutaens kanoniske UUID uanset listernes rækkefølge,
|
||||||
|
navn og symbol. Hver accepteret valuta skal have præcis ét positivt minimum og
|
||||||
|
maksimum, og minimum må ikke overstige maksimum. Alle tre lister kopieres ved
|
||||||
|
oprettelse af burneren; tre tomme lister tillader direkte EVE-burn.
|
||||||
|
|
||||||
|
Burneren vælger det mindste af saldo minus reserve og valutaens eget maksimum,
|
||||||
|
afrundet ned til mintens præcision. Er dette faktiske beløb under valutaens
|
||||||
|
minimum, springes inputtet over, og næste input undersøges i samme iteration.
|
||||||
|
Lighed med minimum accepteres; der rundes aldrig op eller bruges af reserven for
|
||||||
|
at nå minimum. For eksempel springes 0,000001 spendabel USDT over, så en
|
||||||
|
SyrupUSDC-saldo på 0,502048 kan give et swap på 0,2 SyrupUSDC. Inputminimummer
|
||||||
|
begrænser ikke burn af eksisterende EVE. Grænserne angives i tokens: SyrupUSDC er ikke
|
||||||
|
lig med USDC, og 0,2 SyrupUSDC garanterer ikke en bestemt USD-værdi. Der kræves
|
||||||
|
ingen inputtoken/USDC-pris eller nye Ticker-kilder og historikfiler.
|
||||||
|
|
||||||
|
Højst ét input forsøges vekslet pr. iteration, med en times pause efter hver
|
||||||
|
afsluttet iteration. Jupiter vælger pool og rute til EVE. USDT har fortsat første
|
||||||
|
prioritet, så en vedvarende spendabel USDT-saldo kan udsætte SyrupUSDC-veksling.
|
||||||
|
Efter det mulige buyback, også når prisbetingelsen springer købet over,
|
||||||
|
genlæses hele den synlige EVE-saldo, også ved direkte
|
||||||
|
EVE-indbetalinger. Ejeren har valgt et produktionsminimum på **0,1 EVE pr. burn**
|
||||||
|
og en **reserve på 0,01 EVE**. Hub'en angiver begge som `MoneyAmount` med EVE's
|
||||||
|
kanoniske UUID. Minimum skal være positivt, reserve skal være ikke-negativ;
|
||||||
|
en reserve på nul er gyldig. Null-værdier og andre valutaer afvises ved oprettelse.
|
||||||
|
|
||||||
|
Burnbeløbet beregnes som `max(EVE-saldo - EVE-reserve, 0)`, afrundet ned til
|
||||||
|
EVE-mintens præcision. Der brændes kun, når dette faktiske beløb er positivt og
|
||||||
|
mindst minimum. Når tærsklen nås, brændes hele det berettigede beløb over
|
||||||
|
reserven; minimum er en tærskel, og der er intet burn-maksimum. Eventuelle
|
||||||
|
afrundingsrester bliver i wallet'en sammen med reserven. Små buybacks fortsætter,
|
||||||
|
når købspris og inputkrav tillader det, og samler EVE op mellem burns.
|
||||||
|
Under tærsklen sker hverken burn eller
|
||||||
|
burn-notifikation.
|
||||||
|
|
||||||
|
| EVE-saldo | Produktionshandling | EVE tilbage efter succes |
|
||||||
|
| ---: | --- | ---: |
|
||||||
|
| 0,105 | Ingen burn | 0,105 |
|
||||||
|
| 0,11 | Burn 0,1 EVE | 0,01 |
|
||||||
|
| 0,135 | Burn 0,125 EVE | 0,01 |
|
||||||
|
|
||||||
|
Issue #93 bruger minimum 1 EVE og reserve 0,1 EVE som adfærdseksempler:
|
||||||
|
1,05 EVE giver ingen burn, 1,1 EVE giver burn af 1 EVE, og 1,35 EVE giver burn af
|
||||||
|
1,25 EVE. Produktionsværdierne ovenfor blev valgt særskilt den 2026-10-06.
|
||||||
|
|
||||||
|
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. Pending burn og notifikationens EVE-beløb og dollarværdi
|
||||||
|
bruger det faktisk indsendte burnbeløb uden reserven. Et pending burn afklares
|
||||||
|
med sin oprindelige signatur uden ny tærskelvurdering eller genindsendelse.
|
||||||
|
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 mulig ny inputvurdering med frisklæste saldi; ethvert nyt køb skal stadig
|
||||||
|
bestå den aktuelle prisbetingelse. Afklaringen er read-only og fortsætter også
|
||||||
|
ved pris lig med eller over referenceprisen. 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
|
### 2.3 Finansiering af udvikling
|
||||||
Udvikling i Evelyn sker gennem en task-baseret model. Alle opgaver oprettes i IntelliJ YouTrack og bliver
|
Udvikling i Evelyn sker gennem en task-baseret model. Alle opgaver oprettes i IntelliJ YouTrack og bliver
|
||||||
@@ -301,3 +399,221 @@ kapitalallokering over tid (AI-drevet tuning).
|
|||||||
ekstreme markedsbevægelser.
|
ekstreme markedsbevægelser.
|
||||||
- Open API for ekstern integration: Gør det muligt at tilføje eksterne datafeeds, udveksle signaler med andre
|
- Open API for ekstern integration: Gør det muligt at tilføje eksterne datafeeds, udveksle signaler med andre
|
||||||
systemer, eller automatisere funding-opgaver.
|
systemer, eller automatisere funding-opgaver.
|
||||||
|
|
||||||
|
## 11. Statusservice og referencepris
|
||||||
|
|
||||||
|
Evelyn er navnet på det samlede system. `com.r35157.evelyn.service.status.EvelynStatusService` indsamler og
|
||||||
|
persisterer statusindeks for én navngiven instans og giver consumers adgang til den samme referencepris, som
|
||||||
|
Evelyn Price Index bruger. Referenceimplementeringen er `EvelynStatusServiceImpl` under
|
||||||
|
`com.r35157.evelyn.service.status.impl.ref`. Hub'en ejer fortsat startup af de enkelte processer.
|
||||||
|
|
||||||
|
`AssetPrice getReferencePrice(Instant timestamp)` beregner prisen for det angivne tidspunkt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Start: 2026-07-31T22:00:00Z
|
||||||
|
Startpris: 15.00 USDT pr. EVE
|
||||||
|
Årlig faktor: 1.20
|
||||||
|
Årslængde: 31,536,000 sekunder
|
||||||
|
referencePrice = 15.00 × 1.20^(elapsedSeconds / 31,536,000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Resultatet indeholder det kanoniske EVE/USDT-tradingpar, så quote-valutaen er eksplicit. Prisen er 15 ved start og
|
||||||
|
18 efter ét 365-dages år. Statusindekset bruger `((actualPrice / referencePrice) - 1) × 10` med samme samplingtidspunkt
|
||||||
|
og samme beregning og konstanter. Den eksisterende konvention om at behandle USDT som USD i indekset bevares.
|
||||||
|
|
||||||
|
Referenceprisen beregnes ved hvert kald uden tickeropslag, historikadgang, workers eller ændring af historikken.
|
||||||
|
Kaldet virker før sampling starter og mens den er stoppet, også uden provisionerede historikfiler. `null` afvises
|
||||||
|
med `NullPointerException`, og tidspunkter før kurvens start afvises med `IllegalArgumentException`.
|
||||||
|
|
||||||
|
Production og Test bevarer separate navne, historikker, livscyklusser og Mission Control-charts. Historikfilerne
|
||||||
|
ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1. Sampling sker fortsat med cirka ét
|
||||||
|
minuts fast forsinkelse, og et punkt persisteres før publicering i et immutable snapshot. `stop()` rydder kun
|
||||||
|
hukommelsen og beholder navnereservationen, restart genindlæser filen, og `close()` frigiver navnet efter sikker
|
||||||
|
terminering. `EvelynStatusIndexPoint` bliver i sin eksisterende package med alle seks indeksfelter; kun Evelyn
|
||||||
|
Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy-rebalancering er fortsat inaktiv.
|
||||||
|
|
||||||
|
## 12. Perps-service og Mission Control
|
||||||
|
|
||||||
|
`EvelynPerpsService` udleverer et immutable `PerpsSnapshot` gennem det parameterløse
|
||||||
|
`getPositionsSnapshot()`. Læsning foretager ingen integrationskald. Hub'en starter
|
||||||
|
separate processer `evelyn.perps.prod` og `evelyn.perps.test`, hver med sin eksisterende
|
||||||
|
wallet-liste: seks PROD-adresser og én TEST-adresse fra issue #98. Begge får en fælles
|
||||||
|
read-only `JupiterPerpsOverview` og en fælles `PhoenixPerpsService`; sidstnævnte er
|
||||||
|
registreret som `phoenix.perps.default`. De eksisterende Jupiter-services til handel
|
||||||
|
og alarmer beholder deres afhængigheder og cache.
|
||||||
|
|
||||||
|
Servicen læser asynkront ved start og venter 60 sekunder efter hvert fuldført gennemløb.
|
||||||
|
Runder overlapper ikke og afhænger ikke af EMC's synlighed. Jupiter-overblikket bruger
|
||||||
|
ét REST-opslag pr. wallet. Phoenix bruger trader-state og ét TWAP-opslag for wallets
|
||||||
|
med eksisterende subaccounts, også uden åbne positioner. Metadata hentes ved positioner eller fyldte
|
||||||
|
TWAP-ordrer med resterende delordrer; én mark price hentes pr. unikt åbent marked,
|
||||||
|
og der foretages et risikolæs pr. synlig konto. Lokal trader-adresseafledning bruger
|
||||||
|
den eksisterende Solana-afhængighed.
|
||||||
|
Begge har 10 sekunders request-timeout; Phoenix bevarer fælles
|
||||||
|
pacing og 429-cooldown. En fuld opdatering tager opslagstid plus pausen, ikke nødvendigvis
|
||||||
|
ét minut. Implementeringens `close()` afbryder worker'en; consumer-API'et har kun læsning.
|
||||||
|
|
||||||
|
Snapshot'et adskiller isolerede positioner fra Cross-konti med egne balancer. Pengebeløb
|
||||||
|
er præcise USD-decimaler, og null betyder utilgængelig. Før første succes indeholder
|
||||||
|
hver konfigureret wallet/venue en ukendt scope, ingen syntetiske positioner. Succes
|
||||||
|
publiceres pr. wallet/venue; en fejl beholder den kombinations sidste observation og
|
||||||
|
oprindelige succes-tidspunkt. Andre wallets/venues forsøges stadig. Et gyldigt tomt svar
|
||||||
|
rydder den pågældende scope og kan totalisere nul; HTTP-/formatfejl kan ikke. En gyldig
|
||||||
|
Cross-konto med kapital bevares, selv om dens sidste position lukkes.
|
||||||
|
|
||||||
|
EMC viser **Position | Risk | Direction | Margin | Collateral | Leverage | Exposure | Value | uPnL | TWAP**
|
||||||
|
under den synlige rod **Perpetual Contracts**. Isolerede positioner har wallet → venue →
|
||||||
|
position; Phoenix Cross har wallet → venue → Cross → position. Positionens konto-ID
|
||||||
|
bestemmer identitet og tilhørsforhold, aldrig instrumentteksten. Wallets følger den
|
||||||
|
hidtidige konfiguration med Jupiter før Phoenix. En venue uden positioner, Cross-konti eller TWAP-advarsler
|
||||||
|
skjules og vises igen, når der kommer indhold. Skjulte venues indgår fortsat i snapshot
|
||||||
|
og totaler; deres læsestatus findes på wallet-rækkens tooltip. En Cross-konto med kapital
|
||||||
|
holder derfor Phoenix synlig, selv uden åbne positioner. Eksisterende foldestatus, markering og
|
||||||
|
rækkeobjekter bevares; kun ændrede rækkeværdier eller børn opdateres, også når risikodata
|
||||||
|
ældes i et ellers uændret snapshot. Vinduesstart er 1100 × 600,
|
||||||
|
og smallere vinduer kan scrolle vandret, når kolonnernes minimumsbredder kræver det.
|
||||||
|
|
||||||
|
TWAP-progress vises på en eksisterende Phoenix-position, når ordren selv har et faktisk
|
||||||
|
fill og resterende delordrer. Ufyldte ordrer opretter ingen rækker og vises heller
|
||||||
|
ikke på en position, som allerede fandtes. Progressbaren måler udførte delordrer;
|
||||||
|
teksten er estimeret resterende tid fra seneste TWAP-observation, fx `≈ 28:32:10`
|
||||||
|
eller `≈ 128:32:10`, uden timerollover eller live-nedtælling. Tooltip viser
|
||||||
|
`Estimated completion`, totalstørrelse, næste delordrestørrelse, fyldt mængde og
|
||||||
|
observationstid. Restordrer kan være mindre. Dollarstørrelser bruger samme Phoenix-
|
||||||
|
markpris som positionen og kræver ikke Ticker-priser; uden markpris bevares mængderne,
|
||||||
|
mens berørte dollarværdier og totaler bliver utilgængelige. En TWAP-læsefejl beholder
|
||||||
|
hele venue-observationen og viser `Retained` i cellen. Se
|
||||||
|
[TWAP-beregninger og begrænsninger](PhoenixPerps.md#twap-observations) for slots,
|
||||||
|
forfaldne tidsplaner og prisestimater.
|
||||||
|
|
||||||
|
Exposure er brutto: 100 USD Long + 100 USD Short giver 200 USD. Dollar-uPnL summeres
|
||||||
|
med fortegn. Collateral/Value tæller hver isoleret position og hver delt Cross-konto én
|
||||||
|
gang, inklusive ubrugt kapital på de inkluderede Cross-konti. Cross-positionernes
|
||||||
|
marginkrav lægges ikke til faktisk kontokapital. Mangler en nødvendig del, er netop
|
||||||
|
feltets total utilgængelig; en delsum præsenteres aldrig som total. Ingen summering eller
|
||||||
|
gennemsnitsberegning af leverage eller PnL-procenter. Totalerne afhænger ikke af foldning.
|
||||||
|
|
||||||
|
Isoleret leverage er Exposure / positiv Collateral; uPnL-procent er 100 × pris-uPnL /
|
||||||
|
positiv Collateral. På Cross-positioner er Collateral et initialt marginkrav i USD,
|
||||||
|
ikke særskilt ejet kapital. Cross-gruppens leverage er bruttoeksponering / udiskonteret
|
||||||
|
equity før estimerede exitgebyrer. Cross-positionernes leverage og ROI-procent er
|
||||||
|
utilgængelige, fordi Phoenix-konventionerne ikke er verificeret; dollar-uPnL vises.
|
||||||
|
Cross-positioners Value er ikke entydigt defineret og vises som —. Risk viser særskilt
|
||||||
|
vedligeholdelsesmarginens udnyttelse; det er ikke samlet initial-marginudnyttelse eller fri købekraft.
|
||||||
|
|
||||||
|
Value er et estimat ved referencepriser, ikke et eksekverbart tilbud. Phoenix medregner
|
||||||
|
collateral, pris-uPnL og signed uafregnet funding og fratrækker estimerede takergebyrer
|
||||||
|
med kontoens fee override. Slippage, price impact, swaps, netværksgebyrer og kontorent
|
||||||
|
indgår ikke. Ukendt spot-collateral gør de berørte værdiansættelser utilgængelige.
|
||||||
|
Jupiters rapporterede `valueUsd` vises uændret som et estimat, fx `≈$123.23`, efter
|
||||||
|
præcis konvertering fra heltals-mikro-USD. Tooltip forklarer, at det faktiske provenu
|
||||||
|
ved lukning kan afvige. En tidligere observation trak åbningsgebyrer fra, selvom
|
||||||
|
collateral allerede var registreret efter gebyrer; gebyrbehandlingen er derfor
|
||||||
|
usikker, og collateral-/profitloft-semantikken er fortsat ikke verificeret. Det
|
||||||
|
beviser ikke, at alle API-estimater er forkerte, eller at forskellen kun er små gebyrer.
|
||||||
|
Der tilføjes ingen gebyrer tilbage og foretages ingen korrigeret netberegning.
|
||||||
|
|
||||||
|
Tilgængelige Value-estimater fra både Jupiter og Phoenix samt deres venue-, wallet-
|
||||||
|
og **Perpetual Contracts**-totaler har ét `≈` foran beløbet. Totalens tooltip forklarer
|
||||||
|
de bidragende estimatkilder, også ved blandede venues og sammenfoldede grupper.
|
||||||
|
Nul og negative beløb bevares. Manglende/null Value gør fortsat den berørte total `—`
|
||||||
|
uden numerisk estimatmarkering; en succesfuldt læst tom gruppe kan give nul. Ugyldige
|
||||||
|
beløb fejler hele observationen, så tidligere data og oprindelig læsetid bevares med
|
||||||
|
fejlstatus. En ny succes erstatter den tidligere værdi, også når den nye Value mangler. Se
|
||||||
|
[Jupiter-overblikket](JupiterPerpsOverview.md) og [Phoenix-integrationen](PhoenixPerps.md)
|
||||||
|
for datakilder, enheder og beregninger.
|
||||||
|
|
||||||
|
Beløb har tusindtalsseparator og to decimaler, leverage × og pris-uPnL et synligt fortegn.
|
||||||
|
Positiv uPnL er grøn, negativ rød, afrundet nul neutral uden negativt nul. De svage
|
||||||
|
Long/Short-baggrunde bevares; grupper har tydeligere skrift og ingen retningsfarve.
|
||||||
|
Tooltips forklarer definitioner, manglende værdier og sidste succesfulde læsetidspunkt.
|
||||||
|
Efter fejl markeres en synlig venue som retained; læsestatus for skjulte venues findes
|
||||||
|
på wallet-rækkens tooltip. Gamle tal beskrives ikke som nye.
|
||||||
|
|
||||||
|
EMC spørger hver miljøservice straks ved åbning og hvert tredje sekund uden at blokere
|
||||||
|
JavaFX-tråden. Skjul/luk stopper kun EMC-polling. Der er ingen sample-data, Refresh-knap,
|
||||||
|
handel, signering, kontoaktivering eller automatisk genstart af Hub'en i denne læsevej.
|
||||||
|
|
||||||
|
### TWAP-advarsler
|
||||||
|
|
||||||
|
En fyldt Phoenix-TWAP med resterende delordrer, som mangler markedsmetadata eller
|
||||||
|
ikke matcher en åben position, bevares som en advarsel. TWAP-cellen viser en lille
|
||||||
|
orange trekant med udråbstegn og kort tekst. Advarslen står på positionen, når
|
||||||
|
ordren sikkert kan tilknyttes den; ellers på den ejende Cross-gruppe. Gruppen
|
||||||
|
bevares også uden kapital eller positioner. En umatchet isoleret ordre står på
|
||||||
|
Phoenix med sit korrekte subaccount i tooltip, aldrig på en opdigtet Cross-konto.
|
||||||
|
En matchet ordre uden brugbart estimat for næste udførelse viser en timing-advarsel
|
||||||
|
sammen med sin progressbar; det er ikke bevis på, at udførelsen er stoppet.
|
||||||
|
|
||||||
|
Advarsler samles op gennem **position → Cross → Phoenix → wallet → Perpetual
|
||||||
|
Contracts**, eller den faktiske isolerede gren uden Cross. Hvert niveau tæller
|
||||||
|
hver underliggende ordre én gang, også når undergrene er foldet sammen. Flere
|
||||||
|
ordrer vises med antal. Tooltip kan rulles og bliver stående, mens markøren er over
|
||||||
|
cellen eller detaljerne. Det identificerer wallet, konto/position, årsag,
|
||||||
|
TWAP-adresse, asset/subaccount, rå tællere/fills og oprindeligt observationstidspunkt.
|
||||||
|
Ukendte symboler, enheder, dollarværdier og tider opfindes ikke. Marginrisiko og
|
||||||
|
kapitaltotaler ændres ikke af advarslerne.
|
||||||
|
|
||||||
|
Ordren skal stadig selv have faktiske fills og resterende delordrer. Ufyldte og
|
||||||
|
færdige ordrer er skjult, også i gruppeoptællinger. Succesfulde opslag rydder løste
|
||||||
|
advarsler helt op gennem træet og bevarer de øvrige. Fejl bevarer tidligere data og
|
||||||
|
markerer berørte advarsler og opsummeringer `retained`; efter fem minutter eller ved
|
||||||
|
fremtidigt tidsstempel markeres de `stale`. Originale tider bevares, også ved fejl i
|
||||||
|
EMC's snapshot-læsning. Alderskontrol bruger eksisterende UI-polling; fem minutter
|
||||||
|
er en datakvalitetsgrænse, ikke en udførelsesfrist. Se [Phoenix-læsningen](PhoenixPerps.md#twap-observations).
|
||||||
|
|
||||||
|
### Risiko i Perps
|
||||||
|
|
||||||
|
Risk står ved positionsnavnet og bruger tekst og en statusprik, mens Long/Short-
|
||||||
|
baggrundene bevares. På isolerede positioner vises rapporteret likvideringsafstand:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Long: 100 × (venue-referencepris − likvideringspris) / venue-referencepris
|
||||||
|
Short: 100 × (likvideringspris − venue-referencepris) / venue-referencepris
|
||||||
|
```
|
||||||
|
|
||||||
|
Ejeren valgte 24. oktober 2026 **neutral positiv afstand og rød ved nået/overskredet
|
||||||
|
grænse**. Der er ingen generelle grøn/gul/rød-grænser pr. marked. Positiv uPnL eller
|
||||||
|
lav leverage tildeler ikke en sikkerhedsstatus. Negativ afstand bevarer fortegnet;
|
||||||
|
det er en observeret grænseoverskridelse, ikke bevis for gennemført likvidering.
|
||||||
|
Manglende, nul og negative sentinel-priser er ukendte, ikke uendelig sikkerhed.
|
||||||
|
Tooltip viser venue-priser, præcis afstand, eventuel rapporteret tier og observationstid.
|
||||||
|
|
||||||
|
Phoenix' egne tiers bruger grøn **OK**, gul **At risk**, orange **Cancellable** og
|
||||||
|
rød **Liquidatable**, **Backstop** eller **High risk**. En selvstændigt gyldig
|
||||||
|
kontoklasse kan bruges, selv om en positiv likvideringspris mangler. På Cross-kontoen:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MM utilization = 100 × maintenance margin / positiv effective collateral
|
||||||
|
USD headroom = effective collateral − maintenance margin
|
||||||
|
```
|
||||||
|
|
||||||
|
MM er marginudnyttelse, ikke likvideringssandsynlighed eller en prisafstand. Phoenix'
|
||||||
|
præcise tier styrer likvideringsstatus; visningsafrunding bruges ikke til klassifikation.
|
||||||
|
Ved ikke-positiv effektiv sikkerhed er procenten utilgængelig. En kendt tom konto
|
||||||
|
uden vedligeholdelseskrav vises som **No positions**; det lover ikke noget om fremtidige
|
||||||
|
ordrefills. Manglende nødvendige data eller modstridende
|
||||||
|
Safe-data må ikke vises grønt. Cross-positioner viser **Shared** med konto-ID i tooltip,
|
||||||
|
og kontoens risiko tælles kun én gang uanset antallet af positioner.
|
||||||
|
|
||||||
|
Grupper viser værste kendte risiko og antal ukendte/forældede scopes uden gennemsnit
|
||||||
|
af afstande eller MM-procenter. En neutral afstand gør ikke hele gruppen grøn; **Distance
|
||||||
|
only** angiver, at der ikke er tildelt en sikkerhedsklasse. Før første succes er en
|
||||||
|
venue ukendt. En tidligere tom venue kan også blive forældet, selv om dens overskrift
|
||||||
|
er skjult. Kendt kritisk risiko forbliver rød med usikkerhedsmarkering efter fejl/aldring.
|
||||||
|
|
||||||
|
Risikodata bliver **Stale** ved fem minutter fra ældste nødvendige observation,
|
||||||
|
straks ved venue- eller snapshot-læsefejl eller ved et fremtidigt tidsstempel. Også
|
||||||
|
et fejlet `getPositionsSnapshot()` genviser den beholdte observation med fejlstatus.
|
||||||
|
Fem minutter er en grænse
|
||||||
|
for datakvalitet, ikke en garanti for sikkerhed i perioden. Langsomme normale runder
|
||||||
|
kan derfor give Stale. Den eksisterende tresekunders UI-polling genvurderer alder,
|
||||||
|
også når servicen returnerer samme snapshot, uden en ny timer eller flere venue-kald.
|
||||||
|
Forældet eller ufuldstændig ikke-kritisk risiko får ingen grøn statusprik.
|
||||||
|
Tidsstempler er modtagelsestider; API-lag og separate pris-/kontolæs begrænser aktualiteten.
|
||||||
|
Visningen viser senest observeret risiko og er ikke en livealarm. Datakilderne er
|
||||||
|
beskrevet i [Jupiter-overblikket](JupiterPerpsOverview.md) og
|
||||||
|
[Phoenix-risikolæsningen](PhoenixPerps.md#venue-reported-account-risk).
|
||||||
|
|
||||||
|
Den kanoniske systembeskrivelse findes i
|
||||||
|
[Evelyn: services og status](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice).
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Evelyn liquidity proceeds
|
||||||
|
|
||||||
|
`com.r35157.evelyn.service.liquidity.EvelynLiquidityService` is a domain-only
|
||||||
|
Nenjim component. `impl.ref.EvelynLiquidityServiceImpl` additionally implements
|
||||||
|
`NenjimProcess`; NenjimHub owns startup. The optional process ID is
|
||||||
|
`evelyn.liquidity.prod`.
|
||||||
|
|
||||||
|
The owner creates/funds EVE/SyrupUSDC CLMM positions manually (for example,
|
||||||
|
10 EVE each) using the dedicated liquidity wallet. This service withdraws,
|
||||||
|
converts and pays proceeds; it never creates, refills, reranges, closes or burns
|
||||||
|
positions, NFTs or source token accounts. A falling price leaves nonqualifying
|
||||||
|
positions intact, including positions whose inventory has returned to EVE.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Copy `conf/evelyn-liquidity.conf.example` to `conf/evelyn-liquidity.conf` and
|
||||||
|
replace every placeholder. The local runtime file is intentionally ignored by
|
||||||
|
Git. Both begin with `FORMAT_VERSION=1`; the example is never loaded as a
|
||||||
|
fallback. Unknown, duplicate and missing settings fail validation.
|
||||||
|
|
||||||
|
| Setting | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `LIQUIDITY_WALLET` | Dedicated public Solana wallet address |
|
||||||
|
| `SIGNER_KEY_NAME` | Existing external jup key name controlling exactly that address |
|
||||||
|
| `CLMM_POOL` | Existing EVE/SyrupUSDC Raydium CLMM pool address |
|
||||||
|
| `HARVEST_DISTANCE_PERCENT` | `0 <= h < 100`; `10` means ten percent |
|
||||||
|
| `ITERATION_INTERVAL_SECONDS` | Positive integer delay after an iteration completes; example `300` |
|
||||||
|
| `USDC_RECIPIENT` | Recipient wallet owner address, not a token account; must differ from the liquidity wallet |
|
||||||
|
| `MINIMUM_SYRUPUSDC_RESERVE` | Nonnegative SyrupUSDC amount; example `0` |
|
||||||
|
| `MINIMUM_USDC_RESERVE` | Nonnegative USDC amount; example `0` |
|
||||||
|
| `MAXIMUM_SWAP_SLIPPAGE_BPS` | Explicit Jupiter limit in `[0,10000]` basis points |
|
||||||
|
| `MAXIMUM_WITHDRAWAL_SLIPPAGE_BPS` | Explicit CLMM limit in `[0,10000)` basis points |
|
||||||
|
|
||||||
|
The existing SolanaWallet implementation checks the external jup version and
|
||||||
|
key/address binding during construction. No key material belongs in these files.
|
||||||
|
Fund the wallet with SOL for fees and any destination token-account rent.
|
||||||
|
Reserve amounts must be exactly representable at the corresponding mint's
|
||||||
|
precision; `0.01` SyrupUSDC and `0.5` USDC are permitted operating choices.
|
||||||
|
No hidden positive reserve is added.
|
||||||
|
|
||||||
|
Only after configuring and funding the wallet, explicitly add
|
||||||
|
`evelyn.liquidity.prod` to the existing `[AUTO_START]` section of private
|
||||||
|
`conf/NenjimHub.conf`. Keep Registry first and Journal second, with no blank line
|
||||||
|
inside the active section. Preserve all existing process selections and their
|
||||||
|
order. This process requires no Ticker, Evelyn status, JavaFX or burner process.
|
||||||
|
The checked-in Hub example continues to select only Registry and Journal.
|
||||||
|
|
||||||
|
Deployment configuration and the dedicated signer/wallet, Jupiter and direct
|
||||||
|
Raydium dependencies are constructed only when the selected entry is reached.
|
||||||
|
Registry IDs are `solana.wallet.evelyn-liquidity-prod`,
|
||||||
|
`jupiter.swap.evelyn-liquidity-prod`, `raydium.service.evelyn-liquidity-prod`
|
||||||
|
and `evelyn.liquidity.prod`. Jupiter signs with this wallet. Currency/mint
|
||||||
|
identities come from CIS; token programs and precision come from chain metadata.
|
||||||
|
|
||||||
|
## Iteration and recovery
|
||||||
|
|
||||||
|
A single worker runs immediately on startup and then with fixed delay after
|
||||||
|
completion. Every iteration follows the same three independent steps:
|
||||||
|
|
||||||
|
1. Discover the wallet's position NFTs. For each owned, positive-liquidity
|
||||||
|
position in the configured pool, read fresh on-chain pool state in
|
||||||
|
SyrupUSDC per EVE after discovery and any earlier withdrawals. Withdraw complete
|
||||||
|
liquidity when its normalized **upper** boundary satisfies
|
||||||
|
`U <= P * (1 - h/100)`. Equality qualifies. Both token orientations use
|
||||||
|
exact squared Q64.64 comparisons, without display-price rounding. At
|
||||||
|
`P=15, h=10`, `13–13.5` qualifies and `13.5–14` does not. Withdrawals retain
|
||||||
|
the position and NFT, and return pool-token fees to the same wallet.
|
||||||
|
Each position uses its own fresh state for both eligibility and withdrawal
|
||||||
|
minimums; the price is not reused across positions.
|
||||||
|
2. Read current SyrupUSDC balance and swap the full balance above its reserve
|
||||||
|
into USDC through the confirmed exact-input Jupiter operation.
|
||||||
|
3. Read current USDC balance and send the full balance above its reserve to
|
||||||
|
the recipient owner, creating its ATA at the sender's expense if needed.
|
||||||
|
The transfer submits once and is awaited at `CONFIRMED`.
|
||||||
|
|
||||||
|
Missing/zero balances and balances equal to their reserves are skipped. Amounts
|
||||||
|
are rounded down to mint precision. An unavailable or unusable pool price stops
|
||||||
|
the remaining withdrawals in that iteration while retaining steps 2 and 3. EVE returned as principal/fees and
|
||||||
|
other reward tokens remain in the wallet and never enter the conversion/payout
|
||||||
|
pipeline. All available SyrupUSDC/USDC is eligible, including manual deposits.
|
||||||
|
|
||||||
|
An ordinary position failure does not block other readable positions. An
|
||||||
|
ordinary step failure does not block later independent steps. Logs identify
|
||||||
|
steps, position NFTs, liquidity/token amounts and returned signatures. Unknown
|
||||||
|
outcomes are logged with signatures when available. Subsequent iterations and
|
||||||
|
restarts reread chain state; there is no service journal, attribution, pending
|
||||||
|
signature gate, immediate retry or transaction replay. Empty positions are
|
||||||
|
skipped and can be manually reused. Shutdown interrupts the current operation
|
||||||
|
and prevents following steps; the reference implementation can also be closed
|
||||||
|
by its local owner through `AutoCloseable`.
|
||||||
|
|
||||||
|
## Withdrawal support
|
||||||
|
|
||||||
|
The transaction follows the official
|
||||||
|
[decrease_liquidity_v2 accounts](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity_v2.rs)
|
||||||
|
and [decrease/minimum-output behavior](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity.rs),
|
||||||
|
also checked against the
|
||||||
|
[official SDK encoder](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/clmm/instrument.ts).
|
||||||
|
Withdrawal and closure are separate actions in
|
||||||
|
[Raydium's liquidity documentation](https://github.com/raydium-io/raydium-docs-v1/blob/main/user-flows/add-remove-liquidity.mdx).
|
||||||
|
|
||||||
|
Raydium validates pool/personal-position discriminators, the owned NFT holding,
|
||||||
|
current complete liquidity, mint precision, vaults and tick arrays before signing.
|
||||||
|
For every pool or reward mint, existing owner token accounts are reused after
|
||||||
|
validating their mint, owner, token program and initialized state, including
|
||||||
|
empty accounts and accounts outside the owner's ATA. Only when no owner holding
|
||||||
|
exists does the withdrawal use or idempotently create its ATA. A failed holdings
|
||||||
|
read aborts rather than creating an additional account. This preserves the
|
||||||
|
existing wallet's single-account-per-mint balance/transfer contract. The
|
||||||
|
transaction requests a 600,000 compute-unit limit.
|
||||||
|
The bitmap extension and initialized reward triplets are included when required.
|
||||||
|
The integer principal math matches Raydium's
|
||||||
|
[tick algorithm](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/libraries/tick_math.rs).
|
||||||
|
Minimum principal amounts are floored after applying explicit withdrawal
|
||||||
|
slippage. An amount too small to protect at least one raw unit is rejected.
|
||||||
|
No withdrawal always substitutes zero for both minimums.
|
||||||
|
Price movement after the per-position read remains possible before execution;
|
||||||
|
principal minimums protect received amounts, not the harvest-distance threshold.
|
||||||
|
|
||||||
|
The current EVE mint uses Token-2022 metadata pointer/token metadata extensions;
|
||||||
|
SyrupUSDC uses legacy SPL. Read-only mainnet inspection on 2026-10-04 found the
|
||||||
|
EVE/SyrupUSDC pool `EpZXeeShz64DK8HfKnjWPTkmhN7dZe77SjvzNDX8CARq` with decimals
|
||||||
|
8/6, tick spacing 60 and no initialized rewards. Deployment still requires an
|
||||||
|
explicit pool setting and repeats validation. Mint close-authority and metadata/
|
||||||
|
group extensions are supported; other transfer-affecting extensions, including
|
||||||
|
transfer fees/hooks, fail before signing. The withdrawal encoder supports legacy
|
||||||
|
transactions fitting Solana's packet limit and rejects larger account sets.
|
||||||
|
|
||||||
|
## Offline verification
|
||||||
|
|
||||||
|
The Gradle JUnit fixtures exercise exact threshold equality, inversion, raw
|
||||||
|
principal amounts, owner/pool filtering, reserves, independent failures,
|
||||||
|
restart recovery and shutdown. Captured serialized transactions verify account
|
||||||
|
order, full liquidity, minimum output, NFT retention, bitmap/reward accounts and
|
||||||
|
one submission/confirmation attempt. Review regression fixtures also cover price
|
||||||
|
changes during discovery or earlier withdrawals in both token orientations,
|
||||||
|
quote consistency, and reuse/validation of existing pool and reward token accounts
|
||||||
|
under both token programs. All financial dependencies are controlled;
|
||||||
|
no verification submits live withdrawals, swaps or transfers.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gradlew compileJava compileTestJava test \
|
||||||
|
--tests '*RaydiumClmm*Test' --tests '*EvelynLiquidityServiceImplTest' \
|
||||||
|
--tests '*NenjimHubLiquidityConfigurationTest'
|
||||||
|
openspec validate 89-add-evelyn-liquidity-service --strict
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop the process/JVM and remove its explicit Hub selection to disable operation.
|
||||||
|
Existing positions and wallet balances remain the recovery state.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Jupiter read-only Perps overview
|
||||||
|
|
||||||
|
`JupiterPerpsOverview` is an overview-only interface under
|
||||||
|
`com.r35157.libs.jupiter.perps`; `impl.ref.RestJupiterPerpsOverview` reads
|
||||||
|
`GET https://perps-api.jup.ag/v2/positions?walletAddress=<address>` once per wallet.
|
||||||
|
The Hub injects this reader into both Evelyn Perps processes with their existing
|
||||||
|
separate wallet lists. It has a 10-second request timeout, no immediate retries
|
||||||
|
and no portfolio cache. Signing, transactions, alarms, their existing Solana cache
|
||||||
|
and entry-price behavior remain in the existing `JupiterPerpsService`.
|
||||||
|
|
||||||
|
## Response contract and definitions
|
||||||
|
|
||||||
|
The official [CLI response type](https://github.com/jup-ag/cli/blob/main/src/clients/PerpsClient.ts)
|
||||||
|
and [API reference](https://perps-api.jup.ag/v1/docs) describe the position list.
|
||||||
|
The implementation requires an integer `count` matching the complete `dataList`,
|
||||||
|
unique valid position accounts, valid asset mints, Long/Short side and positive
|
||||||
|
size. HTTP/schema failures throw; only an explicit count-zero empty list clears
|
||||||
|
the scope. Missing optional valuation fields become unavailable; malformed
|
||||||
|
amounts fail the read.
|
||||||
|
|
||||||
|
| Display | Source / definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Exposure | `sizeUsd / 10^6`; preserves the existing recorded USD-size basis |
|
||||||
|
| Collateral | `collateralUsd / 10^6`, the venue's recorded post-fee USD collateral |
|
||||||
|
| Leverage | Exposure / positive Collateral; not equity-based leverage |
|
||||||
|
| uPnL | `pnlBeforeFeesUsd / 10^6`, price PnL before separately reported fees |
|
||||||
|
| uPnL percent | 100 × uPnL / positive Collateral |
|
||||||
|
| Value | `valueUsd / 10^6`, Jupiter's reported estimate, unchanged and displayed with `≈` |
|
||||||
|
| Risk reference price | Positive `markPriceUsd / 10^6` from the same response |
|
||||||
|
| Reported liquidation price | Positive `liquidationPriceUsd / 10^6`; missing/nonpositive values are unavailable |
|
||||||
|
|
||||||
|
All amounts are parsed as decimal integers and converted exactly. The API's
|
||||||
|
`pnlBeforeFeesPct` is already percentage points (95.48 means 95.48%), but the
|
||||||
|
overview derives the requested percentage against precise collateral itself.
|
||||||
|
The API's rounded `leverage` is likewise not used for the derived ratio.
|
||||||
|
|
||||||
|
The overview preserves one original response observation time on all positions.
|
||||||
|
It does not treat a position's `updatedTime` as proof of current price freshness.
|
||||||
|
Risk uses the venue's reported prices, with no new liquidation-price formula or
|
||||||
|
Ticker dependency. Missing/sentinel prices retain the position and its independent
|
||||||
|
metrics. Present malformed amounts still fail the read. Isolated signed distance,
|
||||||
|
the owner's neutral-distance policy and expiration are described in
|
||||||
|
[Perps risk presentation](Evelyn.md#risiko-i-perps).
|
||||||
|
|
||||||
|
## Reported Value and its uncertainty
|
||||||
|
|
||||||
|
The overview preserves optional `valueUsd` exactly as supplied, including zero
|
||||||
|
and signed values. EMC displays it with `≈` and two USD decimals (for example,
|
||||||
|
`≈$123.23`). Its tooltip explains: “Estimated position value reported by Jupiter.
|
||||||
|
Actual proceeds on closing may differ.” Missing/null values remain unavailable;
|
||||||
|
present malformed amounts fail the complete observation. Successful observations
|
||||||
|
replace earlier values, including replacing an estimate with unavailable Value.
|
||||||
|
Failed reads retain the original observation, success time and failure indication.
|
||||||
|
|
||||||
|
Available Jupiter, wallet and Perpetual Contracts Value totals inherit the estimate
|
||||||
|
qualification independently of expansion state. Mixed Phoenix/Jupiter totals
|
||||||
|
identify both estimate sources; Phoenix's existing account-aware closing estimates
|
||||||
|
and single counting of shared Cross capital are preserved. A missing required Value
|
||||||
|
still makes the affected total `—`, without a numeric approximation marker. A
|
||||||
|
successfully read empty scope can total zero.
|
||||||
|
|
||||||
|
This reports a venue estimate, not verified closing proceeds. It does not add
|
||||||
|
opening fees back, correct the API number, introduce closing quotes or combine
|
||||||
|
additional on-chain observations with the response.
|
||||||
|
|
||||||
|
On 24 October 2026 the funded configured wallet's six API sizes and collateral
|
||||||
|
amounts matched read-only on-chain position data. For its SOL Short, the response
|
||||||
|
contained these integer micro-USD values:
|
||||||
|
|
||||||
|
```text
|
||||||
|
collateralUsd = 18559 pnlBeforeFeesUsd = 17721
|
||||||
|
openFeesUsd = 361 closeFeesUsd = 361
|
||||||
|
borrowFeesUsd = 7 valueUsd = 35551
|
||||||
|
```
|
||||||
|
|
||||||
|
`35551 = 18559 + 17721 - 361 - 361 - 7`. The on-chain collateral was also
|
||||||
|
18559; the official [position account documentation](https://developers.jup.ag/docs/perps/position-account)
|
||||||
|
defines collateral as after fees. This observation raises uncertainty about
|
||||||
|
subtracting a settled opening fee again; it does not prove that every API estimate
|
||||||
|
is wrong. Adding that fee back alone would not establish
|
||||||
|
the full contract: collateral-currency valuation and the `lockedAmount` profit
|
||||||
|
cap remain unverified. The response does not expose that cap. The possible difference
|
||||||
|
from actual closing proceeds is not known to be limited to small fees. Issue #100
|
||||||
|
therefore withheld Value; [issue #104](https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/104)
|
||||||
|
replaces that presentation with the explicitly qualified venue estimate.
|
||||||
|
|
||||||
|
There is no verified closing-value parity with Jupiter. Counts, amounts and prices
|
||||||
|
above are historical observations, not fixed expectations.
|
||||||
|
See the change's `verification.md` for observation times and current checks.
|
||||||
@@ -177,7 +177,9 @@ nenjim.journal.service</code></pre>
|
|||||||
<code>start()</code>, and recorded only after successful return. An omitted process is therefore neither
|
<code>start()</code>, and recorded only after successful return. An omitted process is therefore neither
|
||||||
constructed, registered, started, nor displayed by Process Manager.</p>
|
constructed, registered, started, nor displayed by Process Manager.</p>
|
||||||
<p><code>[AUTO_START]</code> is the complete manually ordered process dependency closure. The Hub does not infer,
|
<p><code>[AUTO_START]</code> is the complete manually ordered process dependency closure. The Hub does not infer,
|
||||||
insert, recursively construct, or reorder dependencies; JavaFX runtime must precede selected JavaFX
|
insert, recursively construct, or reorder dependencies. Ticker and Production Evelyn status must precede
|
||||||
|
a selected Production IOU Burner; the Hub injects that status service for reference-price decisions before
|
||||||
|
new buybacks and fails clearly if the predecessor is unavailable. JavaFX runtime must precede selected JavaFX
|
||||||
applications. Construction, registration, and startup remain fail-fast without rollback. Earlier successful
|
applications. Construction, registration, and startup remain fail-fast without rollback. Earlier successful
|
||||||
snapshot entries remain truthful after a later failure, and the online point plus blocking wait are reached
|
snapshot entries remain truthful after a later failure, and the online point plus blocking wait are reached
|
||||||
only after every configured process succeeds.</p>
|
only after every configured process succeeds.</p>
|
||||||
@@ -186,6 +188,10 @@ nenjim.journal.service</code></pre>
|
|||||||
<code>NenjimProcess</code> so the Hub owns startup. The implementation starts its configured active
|
<code>NenjimProcess</code> so the Hub owns startup. The implementation starts its configured active
|
||||||
<code>PriceSource</code> instances internally and stops those already started if Ticker startup fails; it
|
<code>PriceSource</code> instances internally and stops those already started if Ticker startup fails; it
|
||||||
exposes no general Ticker stop operation.</p>
|
exposes no general Ticker stop operation.</p>
|
||||||
|
<p><code>EvelynIOUBurnerService</code> follows the same separation even though its current domain-service view is
|
||||||
|
empty. It supplies typed Registry and dependency identity without external controls, while the same
|
||||||
|
<code>EvelynIOUBurnerServiceImpl</code> object separately implements <code>NenjimProcess</code> for Hub-owned
|
||||||
|
startup. Neither the service interface nor the implementation exposes an ordinary stop or restart operation.</p>
|
||||||
<p>The Ticker still explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains a passive,
|
<p>The Ticker still explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains a passive,
|
||||||
registered component. Process Manager continues to poll immutable ordered snapshots of actual successful
|
registered component. Process Manager continues to poll immutable ordered snapshots of actual successful
|
||||||
startup milestones rather than displaying configured intent or claiming current health.</p>
|
startup milestones rather than displaying configured intent or claiming current health.</p>
|
||||||
|
|||||||
+36
-2
@@ -204,6 +204,11 @@ samme `TickerServiceImpl`-objekt separat er en `NenjimProcess`, som Hub'en start
|
|||||||
implementation-owned; `PriceSource`-instanser startes internt, og en fejlet Ticker-start stopper de sources, som nåede
|
implementation-owned; `PriceSource`-instanser startes internt, og en fejlet Ticker-start stopper de sources, som nåede
|
||||||
at starte. Der findes ingen almindelig Ticker-serviceoperation til start eller stop.
|
at starte. Der findes ingen almindelig Ticker-serviceoperation til start eller stop.
|
||||||
|
|
||||||
|
Evelyn IOU Burner følger samme opdeling, selv om dens nuværende domain-service-view er tomt:
|
||||||
|
`EvelynIOUBurnerService` giver typed Registry- og dependency-identitet uden eksterne håndtag, mens det samme
|
||||||
|
`EvelynIOUBurnerServiceImpl`-objekt separat er en Hub-styret `NenjimProcess`. Kun process-view'et eksponerer startup;
|
||||||
|
hverken service-interfacet eller implementationen eksponerer almindelig stop eller restart.
|
||||||
|
|
||||||
En `NenjimComponentId` er registration metadata i ét Registry og er ikke en egenskab ved objektet. ID-formatet er
|
En `NenjimComponentId` er registration metadata i ét Registry og er ikke en egenskab ved objektet. ID-formatet er
|
||||||
lower-case ASCII med dot-separated segments og valgfrie interne bindestreger, f.eks. `nenjim.registry.service` og
|
lower-case ASCII med dot-separated segments og valgfrie interne bindestreger, f.eks. `nenjim.registry.service` og
|
||||||
`assetaz.price-source.raydium-pool.eve-usdt`. Det samme objekt må registreres under flere forskellige ID'er, og én
|
`assetaz.price-source.raydium-pool.eve-usdt`. Det samme objekt må registreres under flere forskellige ID'er, og én
|
||||||
@@ -263,14 +268,43 @@ nenjim.registry.service
|
|||||||
nenjim.journal.service
|
nenjim.journal.service
|
||||||
assetaz.ticker.default
|
assetaz.ticker.default
|
||||||
jupiter-perps-alarm.default
|
jupiter-perps-alarm.default
|
||||||
evelyn.service.prod
|
evelyn.status.prod
|
||||||
evelyn.service.test
|
evelyn.status.test
|
||||||
evelyn.iou-burner.prod
|
evelyn.iou-burner.prod
|
||||||
|
evelyn.perps.prod
|
||||||
|
evelyn.perps.test
|
||||||
nenjim.javafx-runtime.service
|
nenjim.javafx-runtime.service
|
||||||
evelyn.mission-control.default
|
evelyn.mission-control.default
|
||||||
nenjim.process-manager.default
|
nenjim.process-manager.default
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ved opgradering erstattes `evelyn.service.prod` med `evelyn.status.prod` og
|
||||||
|
`evelyn.service.test` med `evelyn.status.test` i deploymentets private `conf/NenjimHub.conf`, hvor de forekommer.
|
||||||
|
Bevar rækkefølge og aktive/udkommenterede valg. De gamle ID'er accepteres ikke længere. Hub'en konstruerer samme
|
||||||
|
navngivne `Production`- og `Test`-instanser gennem `EvelynStatusServiceImpl` og injicerer deres
|
||||||
|
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
||||||
|
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
||||||
|
|
||||||
|
Mission Control kræver både `evelyn.perps.prod` og `evelyn.perps.test` før `evelyn.mission-control.default`
|
||||||
|
i den private `conf/NenjimHub.conf`. Erstat den tidligere `evelyn.perps.default`-linje med de to nye ID'er;
|
||||||
|
det gamle ID accepteres ikke længere. Hub'en konstruerer, registrerer og starter to separate
|
||||||
|
`EvelynPerpsServiceImpl`-processer med separate hardcodede wallet-lister og tilhørende Jupiter-integrationer.
|
||||||
|
Begge får desuden den fælles `PhoenixPerpsService` uden positionscache, som Hub'en registrerer passivt som
|
||||||
|
`phoenix.perps.default`; den skal ikke tilføjes til `[AUTO_START]`.
|
||||||
|
Hub'en injicerer deres read-only
|
||||||
|
service-views til de tilsvarende miljøfaner i EMC. Process Manager viser begge ID'er efter vellykket startup.
|
||||||
|
Den lokale udviklingskonfiguration er opdateret; andre deployments med EMC skal migreres tilsvarende.
|
||||||
|
Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk indsættelse.
|
||||||
|
Det minimale versionsstyrede bootstrap-eksempel er uændret. Hver service ejer sit eget snapshot og
|
||||||
|
opdaterer det fra Jupiter og Phoenix i baggrunden med 60 sekunders pause efter fuldførte gennemløb.
|
||||||
|
Sidste vellykkede data bevares separat pr. wallet og venue. EMC-læsninger bruger kun
|
||||||
|
det publicerede snapshot. Wallet-listerne fra issue #98 indeholder seks PROD-adresser og én TEST-adresse.
|
||||||
|
|
||||||
|
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
|
||||||
|
`evelyn.status.prod` stå før den. Hub'en injicerer Production status-servicen til
|
||||||
|
referenceprisopslag før nye buybacks; en manglende eller senere status-forgænger
|
||||||
|
giver en tydelig konstruktionsfejl uden implicit oprettelse eller omordning.
|
||||||
|
|
||||||
Efter validering konstruerer og starter Hub'en Registry, registrerer det som `nenjim.registry.service` og registrerer
|
Efter validering konstruerer og starter Hub'en Registry, registrerer det som `nenjim.registry.service` og registrerer
|
||||||
startup-milepælen. Derefter konstrueres og registreres hele den nuværende passive graph uden at starte workers eller
|
startup-milepælen. Derefter konstrueres og registreres hele den nuværende passive graph uden at starte workers eller
|
||||||
processer. Først herefter behandles resten af `[AUTO_START]` i den angivne rækkefølge, begyndende med Journal. Hver
|
processer. Først herefter behandles resten af `[AUTO_START]` i den angivne rækkefølge, begyndende med Journal. Hver
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
# Phoenix Perps read-only integration
|
||||||
|
|
||||||
|
`PhoenixPerpsService`, `PhoenixPerpsAccount`, `PhoenixPerpsPosition`, `PhoenixAccountRisk`, `PhoenixTwapOrder` and `PhoenixTwapWarning` live in
|
||||||
|
`com.r35157.libs.phoenix.perps`; the REST implementation is in `impl.ref`.
|
||||||
|
They contain no Evelyn/UI types or signing/trading operations. NenjimHub creates
|
||||||
|
one adapter without a portfolio cache as `phoenix.perps.default` and injects it into both Evelyn
|
||||||
|
Perps processes. Each process owns its own wallet list, refresh worker and snapshot.
|
||||||
|
The adapter also receives the existing `SolanaBlockChain` for local trader PDA
|
||||||
|
derivation only; risk lookup does not use its RPC or signing operations.
|
||||||
|
See [Evelyn](Evelyn.md#12-perps-service-og-mission-control) for consumer behavior and
|
||||||
|
[the canonical system overview](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice)
|
||||||
|
for configuration and deployment status.
|
||||||
|
|
||||||
|
## REST reads and account model
|
||||||
|
|
||||||
|
The implementation uses these official endpoints at `https://perp-api.phoenix.trade`:
|
||||||
|
|
||||||
|
1. [`GET /v1/trader/state/{authority}?traderPdaIndex=0`](https://docs.phoenix.trade/api/trader/get-trader-state)
|
||||||
|
returns the wallet authority's version-1 snapshot and subaccounts. The
|
||||||
|
[account model](https://docs.phoenix.trade/sdk/accounts) currently uses trader PDA
|
||||||
|
index 0 for user accounts. Subaccount 0 is cross-margin; other returned subaccounts
|
||||||
|
are isolated. `getAccounts()` returns actual balances separately from positions;
|
||||||
|
`getOpenPositions()` remains a convenience flattening of those accounts. Keep funded
|
||||||
|
empty Cross accounts in the overview and include only nonzero `basePositionLots`
|
||||||
|
as position rows. An omitted `positions` field is documented as optional and
|
||||||
|
means empty; null or a wrong type is rejected.
|
||||||
|
2. [`GET /v1/view/exchange/markets`](https://docs.phoenix.trade/api/exchange/list-markets)
|
||||||
|
supplies each symbol's `baseLotsDecimals`, `tickSize`, leverage tiers and taker fee.
|
||||||
|
3. [`GET /v1/market/{symbol}/mark-price`](https://docs.phoenix.trade/api/exchange/get-mark-price)
|
||||||
|
supplies `markPrice.price` in USD. The API permits an absent mark price; a held
|
||||||
|
position remains visible with unknown price-dependent metrics when the mark is absent.
|
||||||
|
A present nonpositive or malformed price still fails the lookup.
|
||||||
|
4. [`GET /v1/trader/{authority}/twap?traderPdaIndex=0`](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/api/traders/client.ts)
|
||||||
|
supplies TWAP snapshots for every wallet with existing subaccounts, including accounts
|
||||||
|
without positions. Filled outstanding orders become matched progress or warning observations;
|
||||||
|
zero-fill and completed orders remain invisible. See [TWAP observations](#twap-observations).
|
||||||
|
5. [`GET /v1/view/trader/{pubkey}`](https://docs.phoenix.trade/api/trader/get-trader)
|
||||||
|
supplies risk for each visible account, including funded empty Cross accounts.
|
||||||
|
Its key is the derived trader account, not the wallet or a TWAP key. See
|
||||||
|
[venue-reported account risk](#venue-reported-account-risk).
|
||||||
|
|
||||||
|
A trader state with no subaccounts needs only the first request. Otherwise TWAP is
|
||||||
|
read immediately after state, with its original response receipt time preserved.
|
||||||
|
Metadata is fetched once if there are held positions or filled outstanding TWAPs;
|
||||||
|
marks are fetched only for distinct held symbols, followed by each visible account's
|
||||||
|
risk read. Registered empty accounts without relevant TWAPs need no metadata or
|
||||||
|
marks; funded or warning-only empty Cross accounts also receive their risk read.
|
||||||
|
Request starts are spaced at least one second apart across both environments to avoid
|
||||||
|
bursts of per-market reads. Each HTTP request has a 10-second timeout and uses the
|
||||||
|
Hub's existing HTTP client (no redirects by default). On HTTP 429 the adapter respects
|
||||||
|
`Retry-After` (seconds or HTTP date; 60 seconds if missing/invalid). Further calls
|
||||||
|
fail fast during that shared cooldown, letting Jupiter and other wallet attempts
|
||||||
|
continue; the normal refresh cycle retries later. There are no automatic
|
||||||
|
retries, new API keys or authentication/signing flows. Public reads worked during
|
||||||
|
verification, despite generated API security annotations referring to bearer auth.
|
||||||
|
HTTP failures, including 401/403/404/429, are errors rather than empty portfolios.
|
||||||
|
The shared pacing follows Phoenix's [rate-limit guidance](https://docs.phoenix.trade/sdk/ws-api-best-practices);
|
||||||
|
it does not guarantee a fixed quota.
|
||||||
|
|
||||||
|
## Units, precision and identity
|
||||||
|
|
||||||
|
The official [SDK's `baseLotsToBaseUnits`](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts)
|
||||||
|
uses `lots / 10 ** baseLotsDecimals`; the exponent may be negative. The adapter uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
quantity = BigDecimal(basePositionLots).scaleByPowerOfTen(-baseLotsDecimals)
|
||||||
|
sizeUsd = abs(quantity) * markPrice.price
|
||||||
|
```
|
||||||
|
|
||||||
|
All lot counts use `BigInteger`; conversion and multiplication use `BigDecimal`
|
||||||
|
without a rounding context. Jackson parses JSON price numbers directly as decimal,
|
||||||
|
without a `double` intermediate. For example, 2,144 SOL lots at `baseLotsDecimals=2`
|
||||||
|
are 21.44 SOL; at a mark price of 104.45 USD, notional is 2,239.408 USD. Entry price,
|
||||||
|
collateral, virtual quote lots and margin are not substitutes for current notional.
|
||||||
|
Rounding to cents occurs only in the EMC display formatter. Margin and estimated
|
||||||
|
exit fees additionally follow the venue rounding described below.
|
||||||
|
|
||||||
|
Within each wallet's Phoenix venue, identity is `traderPdaIndex:subaccountIndex:symbol`.
|
||||||
|
Sequence numbers do not affect identity. Rows sort by PDA index, subaccount index
|
||||||
|
and symbol; labels use `SYMBOL-PERP`. Isolated rows remain directly beneath the venue.
|
||||||
|
Cross positions belong beneath their explicit `Cross` account group, which supersedes
|
||||||
|
the former no-subaccount-level restriction from issue #99. Positive
|
||||||
|
quantity means Long and negative means Short. Thus two SOL subaccounts remain two
|
||||||
|
rows even though their labels match.
|
||||||
|
|
||||||
|
Authority/PDA identity, snapshot version, arrays, symbols, duplicate subaccounts or
|
||||||
|
positions, integer lots, metadata and prices are validated. A malformed lookup fails
|
||||||
|
as a whole; it never publishes a partially decoded portfolio. The shared Jackson
|
||||||
|
mapper is copied so strict parsing does not change other integrations.
|
||||||
|
|
||||||
|
## Refresh behavior and limitations
|
||||||
|
|
||||||
|
Each Evelyn process performs an initial background refresh, then waits 60 seconds
|
||||||
|
following each completed cycle. Jupiter and Phoenix are attempted independently for
|
||||||
|
each configured wallet. Successful results replace only that wallet/venue; failures
|
||||||
|
log wallet and venue and retain its last successful result. A valid empty result
|
||||||
|
replaces that venue's positions and account balances. EMC hides the venue heading
|
||||||
|
when it has neither position rows, Cross accounts nor TWAP warnings; its read status remains in
|
||||||
|
the wallet tooltip and the scope still contributes to snapshot totals.
|
||||||
|
Funded Cross accounts and Cross accounts with eligible warnings remain visible when they no longer hold positions. Snapshot reads never fetch
|
||||||
|
external data; EMC's three-second polling remains unchanged.
|
||||||
|
|
||||||
|
Positions and prices are separate REST reads and can belong to different slots.
|
||||||
|
This is a periodic current-notional estimate, not an atomic exchange valuation.
|
||||||
|
A cycle's duration includes all sequential reads and request pacing before the
|
||||||
|
60-second pause (32 active markets and one visible account need at least about 35 seconds for their reads). Failed
|
||||||
|
reads can leave old data visible indefinitely. A visible venue shows `(retained)` after
|
||||||
|
a failed refresh; cell tooltips include the last-success time. Childless venues are hidden,
|
||||||
|
with failed or pending first-read status available in their wallet's tooltip. Such unknown
|
||||||
|
scopes cannot contribute fabricated zero totals.
|
||||||
|
Trader PDA indices other than 0, streaming and position management are out of scope.
|
||||||
|
|
||||||
|
## Venue-reported account risk
|
||||||
|
|
||||||
|
The [official address derivation](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/pdas.ts)
|
||||||
|
uses program `EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih` and seeds
|
||||||
|
`trader`, authority bytes, a one-byte PDA index and a one-byte subaccount index.
|
||||||
|
The adapter uses the existing local Solana derivation, validates the returned
|
||||||
|
authority/trader key/PDA/subaccount, and verifies the nonzero position membership
|
||||||
|
and signed quantities against trader state. A fill during the separate reads can
|
||||||
|
therefore cause a retained failed observation rather than attach risk to mismatched
|
||||||
|
position state. Lookup works without any active TWAP.
|
||||||
|
|
||||||
|
TraderView returns `riskState`, `riskTier`, `effectiveCollateral`,
|
||||||
|
`maintenanceMargin` and position `liquidationPrice`. The actual deployed amount
|
||||||
|
objects contain integer `value` and `decimals`; the reader computes exact decimal
|
||||||
|
amounts with decimal exponents from -18 through 18 and ignores formatted `ui` strings.
|
||||||
|
Positive liquidation prices are
|
||||||
|
retained; `-1`, zero, other nonpositive values and absent prices are unavailable.
|
||||||
|
They are not zero risk or infinite safety. Optional missing fields and unsupported
|
||||||
|
future tiers cannot silently become Safe. Malformed amounts, identity mismatches
|
||||||
|
and rejected HTTP reads fail the complete wallet/venue observation.
|
||||||
|
|
||||||
|
Cross risk belongs to the shared account. The reported effective collateral can
|
||||||
|
include funding, discounted positive PnL and collateral haircuts; it is distinct
|
||||||
|
from the overview's undiscounted equity and estimated closing Value. Risk therefore
|
||||||
|
can be available even where the older capital valuation cannot value spot collateral.
|
||||||
|
The reported thresholds and tier are preserved without inventing a liquidation engine.
|
||||||
|
See [Phoenix account health](https://docs.phoenix.trade/phoenix/margin-and-risk/account-health)
|
||||||
|
and [margin math](https://docs.phoenix.trade/phoenix/margin-and-risk/margin-math).
|
||||||
|
|
||||||
|
Account risk carries its original response receipt time. Isolated risk combines
|
||||||
|
that account's reported liquidation price with its already fetched Phoenix mark;
|
||||||
|
its observation time is the older receipt. Prices and account data are separate
|
||||||
|
reads and can lag the chain. Existing failures retain original timestamps. The
|
||||||
|
[Risk presentation](Evelyn.md#risiko-i-perps) defines five-minute expiration,
|
||||||
|
neutral isolated distance, venue tier colors and group summaries.
|
||||||
|
|
||||||
|
## TWAP observations
|
||||||
|
|
||||||
|
TWAP data is matched by requested authority, PDA index, subaccount and market `assetId`
|
||||||
|
using Phoenix metadata. Only an existing nonzero position carries a progressbar, but
|
||||||
|
an eligible order that cannot be matched is preserved in its account's immutable
|
||||||
|
warning list. The order itself must have `baseLotsFilled > 0` and remaining children.
|
||||||
|
A new order without fills stays invisible everywhere, even if the market already
|
||||||
|
has a position; completed orders also create no warning. No synthetic position or
|
||||||
|
monetary group total is created. A successful snapshot without that active order
|
||||||
|
clears its progress and warning data. A failed or invalid TWAP read fails the whole wallet/venue observation,
|
||||||
|
retaining the previous complete snapshot and its original timestamps as `(retained)`.
|
||||||
|
|
||||||
|
Warnings retain the TWAP address/order sequence, asset and subaccount IDs, raw
|
||||||
|
execution/fill counters and original receipt time. Missing market metadata or no
|
||||||
|
matching open position is an observed mismatch, not proof of delisting, cancellation
|
||||||
|
or stopped execution. Missing conversion data is shown as raw lots/unknown values;
|
||||||
|
warning-only markets do not trigger mark-price requests. A matched order with no
|
||||||
|
usable future next-execution estimate keeps its progress plus a timing warning.
|
||||||
|
Account/schema/counter mismatches still fail the read; valid missing-market warnings
|
||||||
|
do not discard independent positions or account risk. See the [EMC warning presentation](Evelyn.md#twap-advarsler)
|
||||||
|
for exact placement, ancestor propagation and retained/stale qualifications.
|
||||||
|
|
||||||
|
The [SDK snapshot](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/api/traders/twap.ts)
|
||||||
|
reports `nChildOrders` as the total execution count including dust. With explicit
|
||||||
|
`nDustOrders > 0`, regular count is total minus dust count. Legacy zero/omitted dust
|
||||||
|
count with a positive `dustOrderSize` replaces one final regular child with dust.
|
||||||
|
Total quantity is regular count × regular lot size plus dust count × dust lot size;
|
||||||
|
the next size switches to dust when the regular children have executed. Convert all
|
||||||
|
sizes and cumulative `baseLotsFilled` with `baseLotsDecimals`, using exact arithmetic.
|
||||||
|
If size/dust metadata is unavailable, sizes remain unknown while independent counters
|
||||||
|
and fills remain available. Execution progress is executed/total children; it does
|
||||||
|
not assert that the planned quantity has filled completely.
|
||||||
|
|
||||||
|
Time estimates use the current nominal **200 ms per Solana slot**, documented by the
|
||||||
|
[Solana Foundation in October 2026](https://solana.com/fr/upgrades/reduced-slot-times).
|
||||||
|
The older 400 ms assumption no longer applies. At receipt of the TWAP response:
|
||||||
|
|
||||||
|
```text
|
||||||
|
estimated next = observation time + (dueSlot - snapshot.slot) × 200 ms
|
||||||
|
final slot = dueSlot + (childOrdersRemaining - 1) × cooldownSlots
|
||||||
|
estimated end = observation time + (final slot - snapshot.slot) × 200 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Receipt time is an approximate anchor; API lag, variable slot times and delayed
|
||||||
|
execution can shift the estimates. No separate RPC sampling or live timer is used.
|
||||||
|
Already-due schedules yield unavailable estimates, as do overflow or expiry before
|
||||||
|
the scheduled child/completion. `lastValidSlot` is only an expiry boundary.
|
||||||
|
|
||||||
|
EMC appends **TWAP** after uPnL. Its progressbar overlays a snapshot duration with
|
||||||
|
unbounded total hours, such as `≈ 28:32:10` or `≈ 128:32:10`. It updates only with new
|
||||||
|
snapshots and is explicitly as of the tooltip's `Updated` time. Due/unavailable
|
||||||
|
timing shows `Due / unknown`; a failed venue shows `Retained` instead of a fresh
|
||||||
|
countdown. Cells without relevant progress or warnings stay empty. Groups show warning
|
||||||
|
summaries when applicable, never aggregated progressbars. Tooltips show progress, `Estimated completion`, total/next/fill
|
||||||
|
quantities, different dust sizes where relevant, observation time and limitations.
|
||||||
|
|
||||||
|
Total and next-child USD values reuse that position's already fetched Phoenix mark,
|
||||||
|
independently of the Ticker. They are approximate current notionals, excluding fees,
|
||||||
|
not historical spend or a promise about future execution cost. An absent mark keeps
|
||||||
|
token quantities visible with unavailable USD values; affected exposure, margin,
|
||||||
|
PnL/equity/Value and dependent totals are unavailable rather than zero.
|
||||||
|
|
||||||
|
|
||||||
|
## Valuations, funding and margin
|
||||||
|
|
||||||
|
Monetary arithmetic uses exact `BigDecimal`/`BigInteger` values. Quote lots are
|
||||||
|
1e-6 USD. The [official SDK computation](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/compute.ts)
|
||||||
|
and [PnL definition](https://docs.phoenix.trade/phoenix/margin-and-risk/entry-price-pnl)
|
||||||
|
separate price PnL from funding and fees:
|
||||||
|
|
||||||
|
```text
|
||||||
|
price uPnL = quantity × markPrice + virtualQuotePositionLots / 1e6
|
||||||
|
funding = unsettledFundingQuoteLots / 1e6 (signed; positive adds equity)
|
||||||
|
equity = collateral + sum(price uPnL + funding)
|
||||||
|
exit fee = ceil_to_micro_USD(abs(quantity) × markPrice × takerFee × REST takerFeeOverrideMultiplier)
|
||||||
|
Value = equity - sum(estimated exit fees)
|
||||||
|
```
|
||||||
|
|
||||||
|
The REST override is already a decimal multiplier (observed 0.9), not the raw
|
||||||
|
on-chain i8 representation. Missing override, fee, funding or virtual quote data
|
||||||
|
makes affected valuations unavailable, rather than defaulting to zero. Settled
|
||||||
|
trading fees and `accumulatedFundingQuoteLots` are not subtracted again; collateral
|
||||||
|
already reflects settlement. Equity is undiscounted: no positive-uPnL risk discount
|
||||||
|
or spot-collateral haircut is substituted for owned capital.
|
||||||
|
|
||||||
|
`collateral` is quote collateral in micro-USD. Each nonzero `spotCollaterals.balance`
|
||||||
|
currently makes collateral/equity/Value unavailable: this version does not have a
|
||||||
|
verified spot index-price valuation. It never quietly ignores SOL or another
|
||||||
|
collateral asset. Zero spot balances do not change quote-only valuations. Missing
|
||||||
|
collateral is also unavailable. Independent price PnL/exposure remain computable.
|
||||||
|
|
||||||
|
For a Cross position the Collateral column is its **initial margin requirement**,
|
||||||
|
not a share of the account balance. The implementation follows the
|
||||||
|
[SDK margin math](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/math.ts)
|
||||||
|
and [integer price conversion](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts):
|
||||||
|
|
||||||
|
```text
|
||||||
|
markTicks = floor(markPrice × 10^(6-baseLotsDecimals) / tickSize)
|
||||||
|
grossQuoteLots = abs(basePositionLots) × markTicks × tickSize
|
||||||
|
L = first-tier leverage, or truncated linear interpolation of leverage between
|
||||||
|
the surrounding maxSizeBaseLots bounds; beyond the final bound use 1
|
||||||
|
initialMarginUsd = ceil(grossQuoteLots / L) / 1e6
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpolation truncates the complete positive leverage, including when leverage
|
||||||
|
is decreasing. For 150 lots between (100 lots, 10×) and (200 lots, 5×), L is 7,
|
||||||
|
not 7.5 or 8. At 100 USD and exponent 2, margin is 21.428572 USD. Exposure retains
|
||||||
|
the exact original REST mark, preserving issue #99's current-notional meaning.
|
||||||
|
|
||||||
|
Cross account leverage is gross exposure / positive undiscounted equity before
|
||||||
|
estimated exit fees. Cross position leverage and ROI are unavailable because the
|
||||||
|
venue's displayed conventions have not been verified; the UI explains this and
|
||||||
|
shows dollar PnL. Isolated leverage/ROI use actual collateral. Cross position
|
||||||
|
Value is inapplicable; Cross account Value counts all shared capital once,
|
||||||
|
including free collateral when its last position closes. Other aggregate ratios
|
||||||
|
are blank. Risk separately displays reported maintenance-margin utilization
|
||||||
|
against effective collateral. It is not total initial-margin utilization, free
|
||||||
|
buying power or the ratio used for the Leverage column.
|
||||||
|
|
||||||
|
Value is an estimate at separately read reference prices, not an executable quote.
|
||||||
|
Slippage, price impact, swaps, network fees, account rent, future funding and
|
||||||
|
unrelated wallet balances are excluded. No profit cap is applied to Phoenix.
|
||||||
|
Unsupported/missing values and any higher totals requiring them show `—`.
|
||||||
@@ -47,6 +47,8 @@
|
|||||||
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Når processen er valgt, ejer Hub'en dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. En headless konfiguration kan udelade runtime og alle JavaFX-applikationer helt. |
|
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Når processen er valgt, ejer Hub'en dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. En headless konfiguration kan udelade runtime og alle JavaFX-applikationer helt. |
|
||||||
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
||||||
| TickerService | Lifecycle-frit domain/query-view med kun latest-price lookup. Samme `TickerServiceImpl`-objekt er separat en Hub-styret `NenjimProcess`; det ejer `PriceSource`-startup internt og stopper allerede startede sources som rollback, hvis Ticker-start fejler, men eksponerer ingen almindelig start- eller stop-operation. |
|
| TickerService | Lifecycle-frit domain/query-view med kun latest-price lookup. Samme `TickerServiceImpl`-objekt er separat en Hub-styret `NenjimProcess`; det ejer `PriceSource`-startup internt og stopper allerede startede sources som rollback, hvis Ticker-start fejler, men eksponerer ingen almindelig start- eller stop-operation. |
|
||||||
|
| EvelynIOUBurnerService | Tomt, lifecycle-frit domain-service-view, som bevarer typed Registry- og dependency-identitet, indtil eksterne domain-operationer findes. Samme `EvelynIOUBurnerServiceImpl`-objekt er separat en Hub-styret `NenjimProcess`; service-consumers får ingen start-, stop- eller restart-operationer. |
|
||||||
|
| EvelynStatusService | Service for én navngiven Evelyn-instans, som indsamler og persisterer statushistorik og udleverer den beregnede EVE-referencepris med eksplicit USDT-quote. Den bevarer sit eksisterende start/stop/close-contract; samme `EvelynStatusServiceImpl`-objekt startes af Hub'en som `NenjimProcess` under `evelyn.status.prod` eller `evelyn.status.test`. Evelyn er fortsat navnet på det samlede system. |
|
||||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||||
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||||
|
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-09-04
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
The Burner implementation is already both `EvelynIOUBurnerService` and `NenjimProcess`, but the service contract contains no domain operation: it exposes only duplicated startup and an implementation-specific stop lifecycle. The canonical `NenjimProcess` contract supports only Hub-owned `start()` and deliberately provides no common stop or restart.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Preserve `EvelynIOUBurnerService` as an empty typed component contract for Registry lookup and dependency injection.
|
||||||
|
- Keep startup exclusively on the implementation's `NenjimProcess` view.
|
||||||
|
- Remove public normal stop and the internal fields/helpers used only by that operation.
|
||||||
|
- Preserve immediate fixed-delay worker startup and cleanup if worker scheduling fails.
|
||||||
|
- Describe loss of process-local uncertainty state only across a complete process restart.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- No stop or restart addition to `NenjimProcess`.
|
||||||
|
- No Hub shutdown or worker-control API.
|
||||||
|
- No new external Burner domain operation.
|
||||||
|
- No transaction, polling, notification, dependency, or test behavior change.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Keep an empty typed service view
|
||||||
|
|
||||||
|
The empty interface remains a `NenjimComponent`. This preserves the domain-specific type, Registry visibility, and future extension point without leaking Hub-owned lifecycle to consumers or exposing the concrete implementation.
|
||||||
|
|
||||||
|
### Retain only process startup
|
||||||
|
|
||||||
|
`EvelynIOUBurnerServiceImpl.start()` remains unchanged in responsibility and implements `NenjimProcess.start()`. The Hub already constructs and registers the same object, resolves it through `NenjimProcess`, and starts it through that view.
|
||||||
|
|
||||||
|
### Remove general stop state without weakening failed-start cleanup
|
||||||
|
|
||||||
|
Delete public `stop()`, its `stopping` flag, termination wait timeout, and interruption-aware stop helper. The `start()` scheduling failure path continues to call `shutdownNow()`, clear the worker reference, reset process-local state, and throw before startup succeeds. A successful process has no public stop/restart path until Nenjim defines one centrally.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [A successful worker cannot be stopped through this implementation] → This is intentional; lifecycle expansion belongs in the common Hub/process architecture.
|
||||||
|
- [An empty service interface has no callable API] → It still supplies meaningful typed identity and prevents consumers from depending on implementation classes.
|
||||||
|
- [Operational recovery now requires process restart] → Document that restart forgets pending uncertainty state and requires external reconciliation first.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Recompile consumers against the empty service interface.
|
||||||
|
2. Remove direct Burner start/stop calls and select startup through the existing Hub process configuration.
|
||||||
|
3. Restart the complete process only after reconciling uncertain on-chain activity; no data or configuration migration is required.
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
`EvelynIOUBurnerService` currently contains only `start()` and `stop()`, so ordinary service consumers receive process control instead of a domain API. This conflicts with the current Nenjim architecture, where the Hub owns lifecycle through an implementation's separate `NenjimProcess` view.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING**: Make `EvelynIOUBurnerService` an empty lifecycle-free typed service contract until external domain operations exist.
|
||||||
|
- Keep `EvelynIOUBurnerServiceImpl.start()` solely as its `NenjimProcess.start()` implementation.
|
||||||
|
- Remove the implementation's public general stop operation and its now-unused stopping state and termination timeout.
|
||||||
|
- Preserve startup-failure cleanup, worker scheduling, transaction safety, and Hub startup wiring.
|
||||||
|
- Replace explicit stop/start reset semantics with process-restart semantics in JavaDoc, OpenSpec, and Nenjim documentation.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: Separates the empty typed service view from Hub-owned process startup and removes unsupported public stop/restart control.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Changes the public `com.fanitas.evelyn.service.burner.EvelynIOUBurnerService` source contract and public methods of its reference implementation.
|
||||||
|
- Existing direct lifecycle callers must use Hub process configuration for startup and cannot request shutdown until common process stop semantics exist.
|
||||||
|
- Adds no Hub shutdown, `NenjimProcess.stop()`, replacement domain operation, dependency, or automated test.
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn-specific public service boundary
|
||||||
|
The system SHALL expose an empty lifecycle-free `EvelynIOUBurnerService` under `com.fanitas.evelyn.service.burner` as the typed domain service and Registry view until external domain operations exist, and SHALL keep its reference implementation under `com.fanitas.evelyn.service.burner.impl.ref`. The same reference implementation object SHALL implement both `EvelynIOUBurnerService` and `NenjimProcess`; the Hub SHALL start it only through its `NenjimProcess` view. Neither the service interface nor the implementation SHALL expose a general stop, restart, worker-control, or other lifecycle operation. The service SHALL remain specific to Evelyn IOU and SHALL NOT introduce a generic token-burner abstraction, burner-specific input interfaces, records, or class hierarchies.
|
||||||
|
|
||||||
|
#### Scenario: Downstream lifecycle consumer
|
||||||
|
- **WHEN** a downstream component depends on the Evelyn burner API
|
||||||
|
- **THEN** it receives a typed component contract with no callable lifecycle or domain operation and does not depend on reference-implementation types or a generic burner model
|
||||||
|
|
||||||
|
#### Scenario: Input model remains domain-level
|
||||||
|
- **WHEN** the Hub configures accepted buyback currencies while constructing the process implementation
|
||||||
|
- **THEN** it uses existing `MoneyAmount` values rather than burner-specific Solana, SPL-token, or input-asset types
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
#### 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 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.
|
||||||
|
|
||||||
|
#### 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 and all later swaps remain suspended until external reconciliation and a complete process restart
|
||||||
|
|
||||||
|
#### 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: Single complete-balance burn submission and pending state
|
||||||
|
For a positive EVE balance with a usable price, the service SHALL call the wallet burn operation exactly once with the fixed EVE mint and complete observed balance. It SHALL NOT automatically retry, rebuild, re-sign, or resubmit within or across iterations. Immediately after a non-blank signature is returned, it SHALL retain process-local pending data containing that signature, submitted EVE amount, and its calculated notification value or complete notification text before awaiting the signature.
|
||||||
|
|
||||||
|
If burn submission fails without a usable signature and its outcome may be unknown, including an I/O or interruption path whose contract permits post-submission uncertainty, the service SHALL conservatively suspend further burn submissions for the current process. It MAY continue ordinary iteration work that does not submit another burn. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation.
|
||||||
|
|
||||||
|
#### Scenario: Burn returns a signature
|
||||||
|
- **WHEN** the wallet submits the complete observed EVE balance and returns a non-blank signature
|
||||||
|
- **THEN** pending state is retained before the service begins finalization waiting
|
||||||
|
|
||||||
|
#### Scenario: Submission outcome may be unknown without a signature
|
||||||
|
- **WHEN** burn submission fails or is interrupted without providing a usable signature and may already have reached Solana
|
||||||
|
- **THEN** the service performs no further burn submission until external reconciliation and a complete process restart
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
## 1. Burner API and implementation
|
||||||
|
|
||||||
|
- [x] 1.1 Remove `start()` and `stop()` from `EvelynIOUBurnerService` while retaining its typed `NenjimComponent` identity.
|
||||||
|
- [x] 1.2 Keep implementation startup through `NenjimProcess` and remove public stop plus stop-only state and helpers.
|
||||||
|
- [x] 1.3 Preserve worker-scheduling failure cleanup and unchanged Hub startup wiring.
|
||||||
|
|
||||||
|
## 2. Specification and documentation
|
||||||
|
|
||||||
|
- [x] 2.1 Update the Burner capability for a lifecycle-free service view, Hub-owned startup, and process-restart-only reset semantics.
|
||||||
|
- [x] 2.2 Update relevant JavaDoc and Nenjim architecture, public, and terminology documentation.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Run strict OpenSpec validation and archive the completed change into the canonical specification.
|
||||||
|
- [x] 3.2 Run the complete Gradle build and inspect the compiled public API and final diff.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-09-04
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. `SolanaWalletImpl` invokes the external `jup` CLI only when signing, while the Hub constructs and registers several wallet instances during passive component initialization. The constructor currently has no checked-exception contract, and the existing signing flow already validates the signer address returned after an actual transaction is signed.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Establish all locally knowable CLI and signer invariants when a wallet is constructed.
|
||||||
|
- Preserve clear startup propagation without expanding the public wallet API.
|
||||||
|
- Keep diagnostics useful while exposing no private key data.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Supporting multiple `jup` versions or a compatibility range.
|
||||||
|
- Installing, updating, importing, selecting, or repairing `jup` keys.
|
||||||
|
- Replacing the existing per-transaction signer-address check.
|
||||||
|
- Adding automated tests or changing Detag configuration.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Require an exact CLI version and fail construction on mismatch
|
||||||
|
|
||||||
|
The constructor runs `jup --version` and accepts only `0.10.1`. Exact failure is chosen over a warning because the CLI arguments and JSON representation form an external integration contract that has only been verified for that version. Allowing construction after a mismatch would weaken the component-readiness guarantee that motivates the change.
|
||||||
|
|
||||||
|
A version constant remains next to the wallet implementation so a future CLI upgrade requires an intentional source change and review. A compatibility range was rejected because no evidence currently establishes compatibility with other versions.
|
||||||
|
|
||||||
|
### Validate the key catalogue instead of signing a probe transaction
|
||||||
|
|
||||||
|
After the version check, construction runs `jup -f json keys list`. The JSON catalogue provides the signer name and public address needed to prove configuration without creating, signing, or submitting a transaction. A signing probe was rejected because it would require manufacturing transaction data and would couple startup to additional Solana semantics.
|
||||||
|
|
||||||
|
The catalogue is read as a JSON tree and only the `name` and `address` fields are inspected. This tolerates unrelated metadata added by `jup` while still rejecting a missing array, missing signer, or invalid address. Raw catalogue output is not copied into diagnostics so future CLI additions cannot accidentally disclose credential material.
|
||||||
|
|
||||||
|
### Preserve constructor signatures with unchecked configuration failures
|
||||||
|
|
||||||
|
External process, decoding, version, key, and address failures are wrapped in descriptive `IllegalStateException`s. This preserves existing constructor and Hub method signatures while allowing the failure to propagate out of passive graph construction and prevent the Hub from announcing itself online.
|
||||||
|
|
||||||
|
Invalid direct constructor arguments retain argument/null exception categories. If process waiting is interrupted, the child process is terminated, the interrupted status is restored, and construction fails with the interruption retained as the cause.
|
||||||
|
|
||||||
|
### Retain post-signing address verification
|
||||||
|
|
||||||
|
Construction-time validation proves the configured key catalogue at one point in time. The existing check of the signer address returned by every later signing response remains necessary defence in depth against configuration changes or unexpected CLI behavior after construction.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Each wallet construction starts two subprocesses] → Accept the small startup cost so every instance validates current external state independently.
|
||||||
|
- [An otherwise compatible future `jup` release stops startup] → Make upgrades explicit by reviewing the CLI contract and updating the expected version constant.
|
||||||
|
- [The key catalogue changes after construction] → Retain runtime signer-address validation for every signed transaction.
|
||||||
|
- [A process produces more output than expected] → Keep preflight commands narrowly scoped to version and key metadata and fail on command or read errors.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Before deployment, install `jup` version `0.10.1` and import every configured signer under its expected name and wallet address. Start Nenjim and allow constructor validation to gate the remaining startup sequence. If deployment must be rolled back, restore the previous application artifact; no persisted application data or public API migration is involved.
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Nenjim currently constructs Solana wallet components without proving that their external `jup` signer configuration is usable. A missing production signer was therefore discovered only during transaction signing, after the operation could already have an uncertain outcome.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Make `SolanaWalletImpl` validate its required constructor inputs before use.
|
||||||
|
- Run `jup --version` during construction and require the explicitly supported version `0.10.1`.
|
||||||
|
- Load the `jup` key catalogue during construction, require the configured signer name to exist, and require its public address to match the wallet address.
|
||||||
|
- Fail construction with actionable diagnostics when the executable, version, key catalogue, signer, or address is invalid, without exposing private key material.
|
||||||
|
- Retain signer-address verification after each actual signing operation as defence in depth.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `solana-wallet-signing`: Defines fail-fast construction and external signer validation for the reference Solana wallet implementation.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The reference implementation at `com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl` gains a startup dependency on executable `jup` version `0.10.1` and a matching configured signer. Nenjim Hub initialization will now fail before registering the complete passive graph when a configured wallet cannot later sign transactions. No public API signatures, Detag configuration, generated sources, tests, or unrelated services change.
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define how a reference Solana wallet proves during construction that its external signer is compatible, available, and bound to the configured wallet address.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Wallet signing configuration is complete
|
||||||
|
The reference Solana wallet SHALL require a non-blank wallet address, a non-blank signer key name, and a non-null Solana blockchain dependency before validating any external signer state. Invalid direct inputs SHALL fail construction with an argument or null exception that identifies the invalid field.
|
||||||
|
|
||||||
|
#### Scenario: Complete direct configuration
|
||||||
|
- **WHEN** construction receives a non-blank wallet address, a non-blank signer key name, and a Solana blockchain dependency
|
||||||
|
- **THEN** external signer validation begins
|
||||||
|
|
||||||
|
#### Scenario: Invalid direct configuration
|
||||||
|
- **WHEN** the wallet address or signer key name is null or blank, or the Solana blockchain dependency is null
|
||||||
|
- **THEN** construction fails before executing an external signer command and identifies the invalid field
|
||||||
|
|
||||||
|
### Requirement: Construction requires the supported jup version
|
||||||
|
The reference Solana wallet SHALL execute `jup --version` during every construction, SHALL require that command to start and exit successfully, and SHALL accept exactly version `0.10.1`. A missing executable, process I/O failure, non-zero exit, blank or malformed version, unsupported version, or interruption SHALL prevent construction with an actionable `IllegalStateException`; interruption SHALL also restore the constructing thread's interrupted status.
|
||||||
|
|
||||||
|
#### Scenario: Supported jup version
|
||||||
|
- **WHEN** `jup --version` starts successfully, exits with zero, and reports `0.10.1`
|
||||||
|
- **THEN** construction proceeds to signer-key validation
|
||||||
|
|
||||||
|
#### Scenario: jup cannot execute successfully
|
||||||
|
- **WHEN** `jup --version` cannot be started, its output cannot be read, or it exits with a non-zero status
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the command, signer key name, expected version, and available exit or error details
|
||||||
|
|
||||||
|
#### Scenario: jup version output is invalid
|
||||||
|
- **WHEN** `jup --version` exits successfully but reports a blank or malformed semantic version
|
||||||
|
- **THEN** construction fails with a diagnostic containing the expected version and reported output
|
||||||
|
|
||||||
|
#### Scenario: jup version is unsupported
|
||||||
|
- **WHEN** `jup --version` reports a valid semantic version other than `0.10.1`
|
||||||
|
- **THEN** construction fails with a diagnostic containing both expected and actual versions
|
||||||
|
|
||||||
|
#### Scenario: Version check is interrupted
|
||||||
|
- **WHEN** the constructing thread is interrupted while awaiting `jup --version`
|
||||||
|
- **THEN** construction fails, the child process is terminated, and the thread's interrupted status is restored
|
||||||
|
|
||||||
|
### Requirement: Construction validates signer identity
|
||||||
|
After validating the supported CLI version, the reference Solana wallet SHALL obtain the `jup` key catalogue as JSON, require a key whose name exactly matches the configured signer key name, require that key to contain a non-blank public address, and require that address to equal the configured wallet address. Command, I/O, JSON, missing-key, missing-address, and address-mismatch failures SHALL prevent construction with an actionable `IllegalStateException`.
|
||||||
|
|
||||||
|
#### Scenario: Configured signer matches the wallet
|
||||||
|
- **WHEN** the key catalogue contains the configured signer key name with a public address equal to the configured wallet address
|
||||||
|
- **THEN** wallet construction succeeds
|
||||||
|
|
||||||
|
#### Scenario: Key catalogue cannot be obtained
|
||||||
|
- **WHEN** the key-list command cannot be started, read, or completed successfully
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the command, signer key name, and available exit or error details
|
||||||
|
|
||||||
|
#### Scenario: Key catalogue is malformed
|
||||||
|
- **WHEN** the key-list command succeeds but does not return a JSON array that can be decoded
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the configured signer key name
|
||||||
|
|
||||||
|
#### Scenario: Configured signer is unavailable
|
||||||
|
- **WHEN** the key catalogue does not contain a key whose name exactly matches the configured signer key name
|
||||||
|
- **THEN** construction fails with a diagnostic identifying that signer key name
|
||||||
|
|
||||||
|
#### Scenario: Signer address is missing
|
||||||
|
- **WHEN** the matching key has no non-blank public address
|
||||||
|
- **THEN** construction fails with a diagnostic containing the signer key name and expected wallet address
|
||||||
|
|
||||||
|
#### Scenario: Signer address differs from the wallet
|
||||||
|
- **WHEN** the matching key's public address differs from the configured wallet address
|
||||||
|
- **THEN** construction fails with a diagnostic containing the signer key name, expected wallet address, and actual public address
|
||||||
|
|
||||||
|
#### Scenario: Validation diagnostics protect key material
|
||||||
|
- **WHEN** any signer validation fails
|
||||||
|
- **THEN** its diagnostic contains only the relevant command, name, public address, version, exit status, and stderr context and does not disclose private key material
|
||||||
|
|
||||||
|
### Requirement: Signing retains runtime identity validation
|
||||||
|
The reference Solana wallet SHALL continue to require every successful signing response to identify the configured wallet address before accepting its signed transaction, even after construction-time signer validation has succeeded.
|
||||||
|
|
||||||
|
#### Scenario: Signing response identifies another wallet
|
||||||
|
- **WHEN** a later signing response contains a signer address different from the configured wallet address
|
||||||
|
- **THEN** signing fails without accepting the returned signed transaction
|
||||||
|
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
## 1. Wallet Construction Validation
|
||||||
|
|
||||||
|
- [x] 1.1 Validate the wallet address, signer key name, and blockchain dependency before invoking `jup`.
|
||||||
|
- [x] 1.2 Execute `jup --version` during construction and fail unless it reports exactly `0.10.1`.
|
||||||
|
- [x] 1.3 Decode `jup -f json keys list`, require the configured signer, and require its public address to match the wallet address.
|
||||||
|
- [x] 1.4 Report process, version, key, and address failures without exposing private key data, and preserve interruption status.
|
||||||
|
- [x] 1.5 Retain signer-address validation in the transaction-signing response flow.
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 Compile main and test source sets and run the existing test suite without adding or modifying automated tests.
|
||||||
|
- [x] 2.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 2.3 Review the implementation and artifacts for exact-version failure, signer identity validation, safe diagnostics, and no generated-source edits.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-09-09
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md. The authoritative baseline is Gitea 0.1-dev at f1dd5ed. Nenjim separates public component APIs from impl.ref processes; the Hub owns explicit construction and registry registration. The artifact JournalService is unrelated.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
Implement the two capability contracts. Keep primitive PDE parsing below event codecs and record integrity. Do not extend the v1 scope in the proposal.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Place events in com.r35157.assetaz.events, service API in services.journal, codecs under services.journal.codec and implementations under impl.ref. PDE belongs to com.r35157.libs.pde. Codec components expose read identity, domain type and whether they encode current writes; historical read-only codecs can coexist without implicit highest-version selection.
|
||||||
|
- The Hub registers explicit codec instances and offers assetaz.journal.service through its process constructor catalogue. The journal resolves the registry catalogue at startup, validates duplicate identities/current encoders, and uses a single lifetime file channel and exclusive file lock. No service restart API is added.
|
||||||
|
- One journal monitor serializes durable append, registration and queue publication. Each subscription has its own monitor, FIFO and worker. Every publication carries a sequence, including irrelevant records, so workers advance only after examining the ordered stream. Consumer code never runs under the journal monitor. An empty queue observed under the subscription monitor selects the ONLINE cutover; subsequent publications queue behind it while the same worker calls ONLINE.
|
||||||
|
- Historical readers scan from the beginning to validate chain state but invoke callbacks only from replayFromSequence through captured registrationHead. This avoids adding indexes or snapshots. The reader closes before pending live records drain.
|
||||||
|
- PDE uses the published little-endian integer/length type table; UUID conversion alone is big endian at codec level. Record hashes operate on original bounded ranges, not canonicalized representations.
|
||||||
|
- JournalFormatVersionChanged has common event fields followed by a target version. Only format 1 is implemented; unsupported targets fail before append and during loading. Future parsers can switch after the ordinary event without a new physical record type.
|
||||||
|
- Barriers are interruptible (InterruptedException alongside DegradedException); no timeout or recovery API is introduced.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Slow consumers can accumulate FIFO entries → intentional independent v1 workers; no unrequested tuning or overflow policy.
|
||||||
|
- Callbacks cannot be rolled back by the journal → document consumer atomic update responsibility and preserve safe cursor on failure.
|
||||||
|
- Uncertain force/write can leave a complete or partial record → permanently fail closed and validate the authoritative file next JVM; no automatic repair.
|
||||||
|
- Hash chains do not prevent a writer recomputing history → external anchors remain out of scope.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
New optional process, using data/assetaz/journal/events.pde. No migration of the Nenjim artifact journal or existing service histories. Add the process ID to the example configuration; existing user configurations remain opt-in. No commits or publication in this task.
|
||||||
|
|
||||||
|
## ValueTags
|
||||||
|
|
||||||
|
The owner name uses the existing Name tag (String). No shared Detag definitions are changed. Proposed future tags, pending separate approval, are AssetAZJournalSequence (parent long, Java long) for authoritative cursors, AssetAZEventType (parent String, Java String) for persistent short identifiers, AssetAZEventVersion and AssetAZJournalFormatVersion (parent int, Java int) to distinguish schema and envelope versions, and AssetOwnerId and AssetAZCorrelationId (parent UUID, Java UUID) to distinguish owner identity from workflow grouping. This implementation keeps the issue's primitive/UUID signatures where these tags do not yet exist.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
AssetAZ needs an authoritative durable event history from which services can reconstruct their models and establish causal freshness. Gitea issue #86 defines a deliberately strict first version.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add an append-only PDE journal with SHA-256 integrity, durable sequence commitment and fail-closed writes.
|
||||||
|
- Add domain events and registry-provided positional event codecs, independent of Java class naming.
|
||||||
|
- Add independent virtual-thread subscriptions, replay, exact filtering, progress barriers and isolated degradation.
|
||||||
|
- Add reusable bounded PDE primitive encoding and decoding.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `assetaz-journal`: Persistence, event schemas, replay and consumer consistency contracts.
|
||||||
|
- `pde-encoding`: Minimal bounded PDE primitives and object framing.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
New AssetAZ API and reference implementation packages, a reusable PDE library, and explicit codec/process entries in the existing Hub composition root. No new dependencies. The Nenjim artifact journal is separate and unchanged.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
No snapshots, repair, truncation, retries, consumer recovery, unregister, restart support, adaptive limits, pools, generalized serialization, reflection discovery, migration, PDL, signatures, causation IDs or unrelated thread changes.
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define the authoritative AssetAZ fact journal and independent consumer reconstruction and causal freshness contracts.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Durable authoritative ordering
|
||||||
|
The journal SHALL be append-only and serialize appends, assigning contiguous sequences from 1 independently of occurredAt. Fields 2..6 SHALL be encoded once, hashed, and copied into the final record. Only successful FileChannel.force(true) SHALL commit the candidate head, publish and return its sequence. Missing append codecs and oversized records MUST fail before any append. An uncertain write SHALL permanently fail closed in that JVM without retries, repair or truncation.
|
||||||
|
|
||||||
|
#### Scenario: Failed force
|
||||||
|
- **WHEN** persistence may have left an uncertain tail
|
||||||
|
- **THEN** the candidate is not committed or published and all later appends fail.
|
||||||
|
|
||||||
|
### Requirement: Record integrity and bounded readers
|
||||||
|
Each record SHALL be exactly one top-level PDE OBJECT containing checksum, previousChecksum, sequence, stable short eventType, eventVersion and OBJECT payload in that order. Both hashes SHALL be 32 raw bytes. SHA-256 SHALL cover the original persisted fields 2..6; the first previousChecksum SHALL be SHA-256(empty). Each independent reader SHALL reuse one fixed 1024-byte buffer. The entire record including header MUST be at most 1024 bytes; reading SHALL reject declared excess or truncation immediately after framing and before payload decoding. Startup SHALL validate the entire history, checksums, chain, contiguous sequences, PDE, known codecs and exact schemas before succeeding.
|
||||||
|
|
||||||
|
#### Scenario: Corrupt history
|
||||||
|
- **WHEN** a checksum, chain, sequence, schema, type/version or framing cannot be verified
|
||||||
|
- **THEN** startup fails with the rejection layer identified; no record is skipped.
|
||||||
|
|
||||||
|
### Requirement: Event and codec contract
|
||||||
|
Events SHALL represent facts, not commands. occurredAt SHALL be semantic time; note and correlationId are nullable. Correlation groups independently meaningful workflow events without atomicity or expected count; inseparable changes belong in one event. Codecs SHALL be registry components resolved by stable eventType and persistent version, never constructed in the journal. Domain class names MUST NOT encode persistent versions. Journal integrity, PDE validity and event schema validity SHALL remain separate layers.
|
||||||
|
|
||||||
|
#### Scenario: Historical codec
|
||||||
|
- **WHEN** a known historical persistent version is read
|
||||||
|
- **THEN** its codec yields the current domain event without changing the domain type name.
|
||||||
|
|
||||||
|
### Requirement: Owner payload
|
||||||
|
AssetOwnerRegistered version 1 SHALL contain exactly seven positional values: signed long epoch seconds, nano-of-second, nullable UTF-8 note, nullable UUID correlationId, required UUID assetOwnerId, required textual assetOwnerType, required UTF-8 name. UUIDs SHALL be exactly 16 bytes, most-significant then least-significant long, both big endian. Nanos MUST be 0..999999999; owner type MUST be NATURAL_PERSON or LEGAL_PERSON, never ordinal. Required values, Instant range and exact field count SHALL be validated by the codec. No additional owner metadata SHALL be added.
|
||||||
|
|
||||||
|
#### Scenario: Extra payload value
|
||||||
|
- **WHEN** an otherwise valid owner payload has an eighth value
|
||||||
|
- **THEN** schema validation rejects it.
|
||||||
|
|
||||||
|
### Requirement: Format changes
|
||||||
|
New journals SHALL implicitly start in format 1 with no header. JournalFormatVersionChanged SHALL be an ordinary event in the same envelope and hash chain, with a positive target version and common event fields. A change applies to subsequent records; returning to an earlier supported version SHALL be representable. Unsupported formats MUST fail closed; v1 implements only format 1.
|
||||||
|
|
||||||
|
#### Scenario: Chained format event
|
||||||
|
- **WHEN** a format-1 change to format 1 is appended
|
||||||
|
- **THEN** it is checksummed and replayed normally; unsupported target formats are rejected before append.
|
||||||
|
|
||||||
|
### Requirement: Registration and independent replay
|
||||||
|
Registration SHALL atomically capture registrationHead, validate 1 <= replayFromSequence <= registrationHead + 1, defensively copy a non-null filter without null members and reject duplicate consumer identity (==). Empty filters SHALL mean all; other filters SHALL match exact runtime class only. Registration SHALL return without awaiting replay. Each subscription SHALL own a virtual-thread worker and independent reader, replay through registrationHead, close the reader and drain subsequent FIFO publications. Degraded registrations remain registered. registrationHead MUST NOT be public API.
|
||||||
|
|
||||||
|
#### Scenario: Late subscriber
|
||||||
|
- **WHEN** another consumer registers while an existing one processes events
|
||||||
|
- **THEN** only the new consumer replays; both receive their own ordered stream without gaps or duplicates.
|
||||||
|
|
||||||
|
### Requirement: Progress and barriers
|
||||||
|
processedThrough SHALL mean all records through the cursor were successfully handled or examined as irrelevant, starting at replayFromSequence - 1. A barrier SHALL return whenever the safe cursor meets its minimum, including DEGRADED; otherwise wait while CATCHING_UP/ONLINE or throw DegradedException when DEGRADED. Progress and degradation SHALL wake waiters. The barrier MUST NOT imply a historical snapshot.
|
||||||
|
|
||||||
|
#### Scenario: Filtered progress
|
||||||
|
- **WHEN** a consumer last handled 1000 and examined irrelevant records through 1100
|
||||||
|
- **THEN** its cursor is 1100 and a barrier for 1100 succeeds.
|
||||||
|
|
||||||
|
### Requirement: States and atomic cutover
|
||||||
|
Subscriptions SHALL begin CATCHING_UP, without requiring an initial state callback. After replay and pending events through an atomic cutover, the worker SHALL request ONLINE. Successful return establishes readiness to act; the callback should establish readiness without irreversible actions. Publication concurrent with cutover SHALL be processed before ONLINE or as the first normal event after ONLINE, with no gap. All callbacks for one consumer SHALL be sequential.
|
||||||
|
|
||||||
|
#### Scenario: Append during readiness callback
|
||||||
|
- **WHEN** a new event arrives at the chosen cutover
|
||||||
|
- **THEN** it is queued and processed next after readiness succeeds.
|
||||||
|
|
||||||
|
### Requirement: Isolated degradation and atomic models
|
||||||
|
Callback or ONLINE transition failure SHALL retain the last safe cursor, mark only that subscription DEGRADED, stop delivery, wake barriers, and log the exception plus consumer identity, sequence and state. Consumers SHALL atomically apply each event or leave their externally visible model unchanged. Replay SHALL rebuild state without repeating trades, transfers or notifications. No OFFLINE, unregister or recovery SHALL be implemented.
|
||||||
|
|
||||||
|
#### Scenario: Consumer throws
|
||||||
|
- **WHEN** one subscriber fails applying sequence N
|
||||||
|
- **THEN** it retains its safe cursor, future barriers beyond that cursor fail, and other subscribers continue.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide reusable primitive binary encoding and bounded structural decoding for positional AssetAZ journal data.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Primitive representation
|
||||||
|
The PDE layer SHALL implement Jenkov PDE integers, UTF-8, bytes, typed null and length-delimited OBJECT framing only. Integer magnitudes and lengths use little endian; negative integers encode -(value + 1). It MUST reject wrong types, malformed lengths, invalid UTF-8 and values outside the requested Java int/long range.
|
||||||
|
|
||||||
|
#### Scenario: Integer extremes
|
||||||
|
- **WHEN** signed long extrema and nullable strings/bytes are encoded
|
||||||
|
- **THEN** they round-trip without loss and null is distinguished from empty.
|
||||||
|
|
||||||
|
### Requirement: Bounded decoding
|
||||||
|
Every decoder SHALL operate on byte[], offset and length; child objects SHALL have independent logical bounds. requireEnd SHALL reject unconsumed values using the logical limit, never backing-array size. Framing SHALL expose total object size before payload decoding.
|
||||||
|
|
||||||
|
#### Scenario: Reused backing storage
|
||||||
|
- **WHEN** a short record follows a long record in one buffer
|
||||||
|
- **THEN** stale bytes are invisible and truncated children are rejected.
|
||||||
|
|
||||||
|
### Requirement: Abstraction boundary
|
||||||
|
PDE SHALL know nothing about UUID, Instant, events, sequence numbers or checksums. It MUST NOT implement object mapping, reflection, annotation schemas or PDL.
|
||||||
|
|
||||||
|
#### Scenario: Domain payload decoding
|
||||||
|
- **WHEN** a UUID byte value is decoded
|
||||||
|
- **THEN** PDE validates its binary framing while the codec validates its 16-byte schema.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. Encoding and contracts
|
||||||
|
|
||||||
|
- [x] 1.1 Implement bounded PDE primitives and framing.
|
||||||
|
- [x] 1.2 Add event/service contracts and registry codecs with exact schemas.
|
||||||
|
|
||||||
|
## 2. Journal and consumers
|
||||||
|
|
||||||
|
- [x] 2.1 Implement record hashing, bounded readers, startup validation and durable fail-closed append.
|
||||||
|
- [x] 2.2 Implement independent virtual-thread replay, FIFO delivery, cutover and barriers.
|
||||||
|
- [x] 2.3 Wire passive codecs and journal process in the Hub.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Run build and existing tests; verify issue invariants and diagnostics.
|
||||||
|
- [x] 3.2 Validate OpenSpec and review unstaged changes against scope.
|
||||||
|
|
||||||
|
## Validation results
|
||||||
|
|
||||||
|
- Normal Gradle build passed, including all four existing tests (zero failures/errors/skips).
|
||||||
|
- 78 temporary checks outside the repository passed: PDE ranges and bounds, exact schemas, raw hashes, full startup validation, 1024/1025-byte boundary, replay/live ordering, filtering, safe cursors, barriers, isolated degradation and uncertain force failure before publication.
|
||||||
|
- OpenSpec strict validation and git diff whitespace checks passed. No repository tests were added, following project policy.
|
||||||
|
- The fresh checkout omitted ignored conf/detag.conf; build validation used a local symlink to the existing configuration without changing its definitions, and a workspace-local Gradle cache.
|
||||||
|
- All changes remain unstaged and uncommitted on the fetched Gitea baseline.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-04
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. Status sampling already calls a package-private `EvelynPriceIndexCalculator`, which holds the curve constants and decimal arithmetic. The implementation receives its canonical EVE/USDT pair from the Hub and validates storage only at startup. Constructor name reservations, file restoration and durable append behavior must survive the package move.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Keep one calculation behind both the public reference-price operation and status sampling; preserve pure on-demand reads and separate Production/Test state while making their status-service identity explicit.
|
||||||
|
|
||||||
|
**Non-Goals:** No additional reference-data service, new ValueTags, curve configuration, lifecycle redesign, package-wide cleanup, trading activation, or storage provisioning/migration.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Move only the API, implementation and calculator. Import `EvelynStatusIndexPoint` from its existing package. Retain the implementation's existing constructor shapes and lifecycle methods; a compatibility wrapper would leave two competing names and is unnecessary for the requested breaking rename.
|
||||||
|
2. Implement `getReferencePrice(timestamp)` as a direct call to the calculator's existing expected-price method, wrapped in `AssetPrice` with the constructor-injected trading pair. Sampling continues to call `calculateIndex(actualPrice, samplingInstant)`, which delegates to that same expected-price method. Sharing the helper preserves identical constants and arithmetic without allocating a second service or changing index calculation.
|
||||||
|
3. Give the public operation no lifecycle gate, synchronization, clock read, ticker lookup or storage access. The immutable trading pair and pure calculator suffice before startup and during stop. Null and pre-start rejection are inherited from the calculator.
|
||||||
|
4. Rename Hub factory references, dependency fields, constants and process IDs together, and update Mission Control's parameter/field types. Process Manager already displays IDs from Hub snapshots and needs no new hardcoded mapping. Keep instance names `Production` and `Test` and the `data/evelyn` root.
|
||||||
|
5. Document the private configuration substitutions without editing the deployment configuration. The versioned example currently selects only Registry and Journal, so its selected processes stay unchanged.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Consumers using old packages or IDs fail] → Update all versioned consumers and document exact deployment substitutions; intentionally provide no alias.
|
||||||
|
- [Fractional exponentiation uses floating-point transcendental arithmetic] → Preserve the existing calculator unchanged and check the start, annual and representative fractional values.
|
||||||
|
- [Verification could trigger operational side effects] → Compile normally and use offline deterministic checks with fake Ticker and temporary storage; never run the Hub or financial processes.
|
||||||
|
- [No existing status/calculator verification source is present in this checkout] → Add only the deterministic acceptance checks explicitly required by #88, covering the public API and regression behavior rather than the rename itself.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Deploy the renamed code with `evelyn.service.prod` replaced by `evelyn.status.prod` and `evelyn.service.test` by `evelyn.status.test` wherever present in private `conf/NenjimHub.conf`. Preserve line order and enabled/commented selection. Retain existing status files byte-for-byte; the service reads the same paths and format. Rollback restores the old code and old IDs without a data migration.
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The current `Evelyn` status-history service is named as if it owns the entire Evelyn system, and its existing reference-price calculation is unavailable to other services. Issue [#88](https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/88) gives Evelyn consumers one authoritative reference price through the existing status service.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING**: Move and rename the public API to `com.r35157.evelyn.service.status.EvelynStatusService` and its implementation to `com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl`; move the package-private calculator alongside it.
|
||||||
|
- Add `AssetPrice getReferencePrice(Instant timestamp)`, calculating on demand through the existing curve and returning the service's canonical EVE/USDT trading pair independently of sampling and storage.
|
||||||
|
- **BREAKING**: Replace `evelyn.service.prod` / `evelyn.service.test` with `evelyn.status.prod` / `evelyn.status.test` in Hub wiring and recognized process IDs. Document the corresponding private deployment configuration edit.
|
||||||
|
- Preserve named Production/Test storage, format/version, history, sampling, lifecycle, Mission Control charts, and inactive legacy rebalancing.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-status-index-history`: Identify the status-service API and expose the shared reference-price calculation while preserving history and lifecycle behavior.
|
||||||
|
- `nenjim-hub-configuration`: Recognize the renamed status process IDs and preserve the configured selection and dependency order.
|
||||||
|
- `nenjim-component-registry`: Use the renamed IDs in the canonical component-ID contract.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Affected source is limited to the status API, implementation, calculator, Hub composition, Mission Control's typed dependencies, and directly related documentation/specifications and verification. Other Evelyn packages and `EvelynStatusIndexPoint` stay in place. Existing deployments must replace only the two old IDs where present in their private `NenjimHub.conf`, retaining enabled/commented selections and order. No history migration, dependency addition, live financial operation, or commit is required.
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn calculates the live price index
|
||||||
|
`EvelynStatusService` SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant t, on or after 2026-07-31T22:00:00Z, the reference price SHALL be 15 × 1.20^(elapsedSeconds / 31,536,000) and the Evelyn Price Index SHALL be ((actualPrice / referencePrice) - 1) × 10, calculated with adequate decimal precision and without arbitrary history rounding. Sampling and the public reference-price operation SHALL use the same reference calculation and constants at the same sampling instant.
|
||||||
|
|
||||||
|
#### Scenario: Calculate expected price at the configured start
|
||||||
|
- **WHEN** the sampling instant is 2026-07-31T22:00:00Z
|
||||||
|
- **THEN** the expected EVE price is exactly 15 USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate expected price after one year
|
||||||
|
- **WHEN** exactly 31,536,000 seconds have elapsed since the configured start
|
||||||
|
- **THEN** the expected EVE price is exactly 18 USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate representative fractional-year growth
|
||||||
|
- **WHEN** 18 days, 3 hours, 8 minutes, and 14 seconds have elapsed
|
||||||
|
- **THEN** the expected EVE price is approximately 15.136464436276 USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate index from actual price
|
||||||
|
- **WHEN** actual price equals expected price, is 10 percent above, is 10 percent below, is double, or is half the expected price
|
||||||
|
- **THEN** the respective index is 0, 1, -1, 10, or -5
|
||||||
|
|
||||||
|
### Requirement: Evelyn instances have safe unique persistent names
|
||||||
|
Every `EvelynStatusService` instance SHALL have an NFC-normalized Unicode name whose preserved spelling and capitalization selects the direct child directory data/evelyn/<name>. A name SHALL be rejected if it is empty or whitespace-only, has leading or trailing whitespace, is . or .., ends with a period, contains a control character or any of < > : " / \ | ? *, is a case-insensitive Windows-reserved filename including a reserved base with an extension, or normalizes as a path to anything other than one direct child of data/evelyn. Within one JVM, normalized names SHALL be reserved atomically and compared case-insensitively using locale-independent rules.
|
||||||
|
|
||||||
|
#### Scenario: A portable Unicode name is accepted
|
||||||
|
- **WHEN** an instance is constructed with a valid Unicode name containing Danish characters
|
||||||
|
- **THEN** NFC normalization is applied and the normalized spelling and capitalization identify its direct child data directory
|
||||||
|
|
||||||
|
#### Scenario: An unsafe name is rejected without reservation
|
||||||
|
- **WHEN** construction receives an invalid name or any other invalid constructor argument
|
||||||
|
- **THEN** construction fails before reserving a persistent identity
|
||||||
|
|
||||||
|
#### Scenario: Concurrent equivalent names conflict
|
||||||
|
- **WHEN** two constructor calls in one JVM concurrently request names that are equal after NFC normalization and locale-independent case folding
|
||||||
|
- **THEN** exactly one reserves the persistent identity and the other fails
|
||||||
|
|
||||||
|
### Requirement: Evelyn distinguishes restartable stop from permanent close
|
||||||
|
Stopping `EvelynStatusService` SHALL terminate sampling, clear memory, preserve its persistent file, and retain its name reservation so the same object can restart. `EvelynStatusService` SHALL support permanent, idempotent close; closing an active instance SHALL first stop it safely, a successfully closed instance SHALL never start again, and its name SHALL be released only after its sampling thread has definitely terminated. A termination failure SHALL be reported and SHALL retain the reservation.
|
||||||
|
|
||||||
|
#### Scenario: A stopped instance restarts
|
||||||
|
- **WHEN** a stopped but not closed `EvelynStatusService` instance is started again
|
||||||
|
- **THEN** it retains exclusive ownership of the same name and reloads the complete persisted history before sampling
|
||||||
|
|
||||||
|
#### Scenario: An active instance closes successfully
|
||||||
|
- **WHEN** close is called on an active instance and its sampling thread terminates
|
||||||
|
- **THEN** memory is cleared, the name reservation is released, and later start calls fail
|
||||||
|
|
||||||
|
#### Scenario: Close cannot confirm termination
|
||||||
|
- **WHEN** close cannot confirm that the sampling thread terminated
|
||||||
|
- **THEN** close reports failure and retains the name reservation
|
||||||
|
|
||||||
|
#### Scenario: Close is repeated
|
||||||
|
- **WHEN** close is called after the instance was successfully closed
|
||||||
|
- **THEN** it completes without changing state or failing
|
||||||
|
|
||||||
|
### Requirement: Evelyn uses externally provisioned per-instance storage
|
||||||
|
Each named instance SHALL use the existing regular file data/evelyn/<normalized-name>/status.log. Before every start, `EvelynStatusService` SHALL require the direct instance path to be an existing readable and writable directory and status.log to be an existing readable and writable regular file. `EvelynStatusService` SHALL NOT create, replace, repair, truncate, rename, or otherwise provision either path. Any validation or actual open failure SHALL identify the affected path, leave memory unpublished, and prevent sampling from starting.
|
||||||
|
|
||||||
|
#### Scenario: Required storage is absent or unsuitable
|
||||||
|
- **WHEN** the instance directory or status file is missing, the path has the wrong type, or required access is unavailable
|
||||||
|
- **THEN** start fails with the affected path and reason without modifying storage, publishing history, or starting sampling
|
||||||
|
|
||||||
|
#### Scenario: Storage changes while stopped
|
||||||
|
- **WHEN** storage becomes invalid after stop and the same instance is started again
|
||||||
|
- **THEN** the complete startup validation is repeated and restart fails without modifying storage
|
||||||
|
|
||||||
|
### Requirement: Evelyn validates status history format strictly
|
||||||
|
The UTF-8 status.log format SHALL have supported version 1, declared by FORMAT_VERSION=1 as the first actual entry; blank lines and full-line comments MAY precede it when loading, while an externally provisioned new file SHALL place it on the physical first line. Missing, duplicate, malformed, misplaced, or unsupported declarations SHALL fail loading before record parsing. After the declaration, blank lines and comments SHALL be ignored, inline comments MAY follow data, and every other entry SHALL be a valid record with an explicit stable numeric type code.
|
||||||
|
|
||||||
|
Type 1 SHALL encode <UTC uuuuMMddHHmmssSSS'Z' timestamp>:1:<plain BigDecimal Evelyn Price Index>. It SHALL have exactly three fields. Unknown types, invalid timestamps or decimals, missing or surplus fields, and decreasing timestamps SHALL fail the complete load; equal timestamps SHALL be accepted. A valid declaration with no records SHALL represent empty history. Applicable failures SHALL identify the file path, one-based line number, and original line, and `EvelynStatusService` SHALL neither skip, sort, partially publish, nor repair invalid content.
|
||||||
|
|
||||||
|
#### Scenario: A valid versioned history loads
|
||||||
|
- **WHEN** the file has a valid first actual version declaration and valid type-1 records in non-decreasing timestamp order
|
||||||
|
- **THEN** the status service restores every record in file order with zero values for the five unimplemented indexes
|
||||||
|
|
||||||
|
#### Scenario: The format declaration is invalid
|
||||||
|
- **WHEN** the declaration is missing, duplicated, malformed, misplaced, or not version 1
|
||||||
|
- **THEN** startup fails before parsing records and reports useful file diagnostics
|
||||||
|
|
||||||
|
#### Scenario: A record is invalid
|
||||||
|
- **WHEN** a record has an unknown type, malformed field, wrong field count, or timestamp earlier than its predecessor
|
||||||
|
- **THEN** startup fails with path, line number, and original line without exposing partial history or altering the file
|
||||||
|
|
||||||
|
### Requirement: Evelyn samples without overlapping or catch-up attempts
|
||||||
|
Each started `EvelynStatusService` instance SHALL own its sampling executor, attempt its first sample only after complete storage validation and history restoration, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the status-service lifecycle independently of whether Mission Control is open. `EvelynStatusService` SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup or persistence operation without adding a fake or unpersisted point, terminating the status service, or cancelling the next attempt. Stopping `EvelynStatusService` SHALL terminate its sampling executor.
|
||||||
|
|
||||||
|
#### Scenario: Initial Ticker data is unavailable
|
||||||
|
- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price
|
||||||
|
- **THEN** no point is persisted or published, diagnostic context is logged, and the next fixed-delay attempt remains scheduled
|
||||||
|
|
||||||
|
#### Scenario: A later sample succeeds
|
||||||
|
- **WHEN** a later attempt obtains a latest EVE/USDT price whose millisecond timestamp is not earlier than the last persisted timestamp
|
||||||
|
- **THEN** exactly one type-1 record is durably appended before the corresponding point becomes visible in memory
|
||||||
|
|
||||||
|
#### Scenario: Persistence fails for one sample
|
||||||
|
- **WHEN** appending or durably flushing a calculated point fails
|
||||||
|
- **THEN** the point is not published, instance and path context are logged, and the next fixed-delay attempt remains scheduled
|
||||||
|
|
||||||
|
#### Scenario: A sampled timestamp goes backwards
|
||||||
|
- **WHEN** a newly sampled timestamp is earlier than the last persisted timestamp
|
||||||
|
- **THEN** no record or point is added, the failure is logged, and later attempts remain scheduled
|
||||||
|
|
||||||
|
### Requirement: Evelyn provides historical status-index measurements
|
||||||
|
Each named `EvelynStatusService` instance SHALL publish a safe, non-null, oldest-to-newest snapshot only after its complete persisted history has been validated and loaded. Each type-1 point SHALL contain its persisted or sampled timestamp, the Evelyn Price Index, and BigDecimal.ZERO placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use independently named files, history collections, reservations, and lifecycles. A sampled point SHALL be durably appended using robust line-boundary handling before publication. Stop SHALL clear only memory; restart SHALL restore the complete file, and close SHALL preserve the file.
|
||||||
|
|
||||||
|
#### Scenario: Status-index history is requested during sampling
|
||||||
|
- **WHEN** a caller requests a status-service instance's status-index history while persisted points may be appended
|
||||||
|
- **THEN** it receives a safe snapshot containing only completely persisted points and all six typed fields
|
||||||
|
|
||||||
|
#### Scenario: A process starts two named Evelyn environments
|
||||||
|
- **WHEN** Production and Test `EvelynStatusService` instances start with independently provisioned files
|
||||||
|
- **THEN** each restores and extends only its own complete history even if calculated values are identical
|
||||||
|
|
||||||
|
#### Scenario: Existing file lacks a final line separator
|
||||||
|
- **WHEN** a valid existing status file ends with a record but no line separator
|
||||||
|
- **THEN** the next durable append first supplies a line boundary and preserves both records as distinct entries
|
||||||
|
|
||||||
|
#### Scenario: An Evelyn instance stops and restarts
|
||||||
|
- **WHEN** a named instance is stopped and later restarted
|
||||||
|
- **THEN** its memory is cleared at stop and its complete unchanged persistent history is revalidated and restored before sampling resumes
|
||||||
|
|
||||||
|
### Requirement: Evelyn Mission Control visualizes status-index measurements
|
||||||
|
Evelyn Mission Control SHALL display separate live Production and Test Overview charts exclusively from their corresponding authoritative `EvelynStatusService` snapshots and SHALL never read or manage persistence directly. Each chart SHALL contain one enabled series named Evelyn Price Index; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete restored current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, close, persist, or clear either status-service instance.
|
||||||
|
|
||||||
|
#### Scenario: Production and Test Overviews show restored history
|
||||||
|
- **WHEN** Mission Control opens after two named `EvelynStatusService` instances have restored their histories
|
||||||
|
- **THEN** it displays separate one-series charts immediately populated from the corresponding complete snapshots
|
||||||
|
|
||||||
|
#### Scenario: A successful point appears while EMC is open
|
||||||
|
- **WHEN** a status-service history publishes a newly persisted point
|
||||||
|
- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC reopens after collecting hidden-window samples
|
||||||
|
- **WHEN** the same EMC instance is reopened after the status services persisted additional points while its window was hidden
|
||||||
|
- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state
|
||||||
|
|
||||||
|
#### Scenario: Future fields contain larger values
|
||||||
|
- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index
|
||||||
|
- **THEN** that disabled value does not affect the visible Y-axis bounds
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Status service has an explicit public boundary
|
||||||
|
The system SHALL expose `com.r35157.evelyn.service.status.EvelynStatusService` as the status-history and reference-price API. Its process IDs SHALL be `evelyn.status.prod` and `evelyn.status.test`. The service rename SHALL preserve the Production/Test storage identities, existing lifecycle operations, `EvelynStatusIndexPoint` and its six fields. Evelyn SHALL remain the overall system name, with NenjimHub owning individual process startup.
|
||||||
|
|
||||||
|
#### Scenario: Consumer uses the renamed status API
|
||||||
|
- **WHEN** a consumer requests status history or a reference price
|
||||||
|
- **THEN** it uses `EvelynStatusService` without accessing an implementation helper or a new service container
|
||||||
|
|
||||||
|
#### Scenario: Existing Production and Test storage survives the rename
|
||||||
|
- **WHEN** the renamed services are constructed and started with the existing Production and Test identities
|
||||||
|
- **THEN** they use the unchanged `data/evelyn/Production/status.log` and `data/evelyn/Test/status.log` files and format version 1 without migration or provisioning
|
||||||
|
|
||||||
|
### Requirement: Reference price is calculated on demand with an explicit quote currency
|
||||||
|
`EvelynStatusService` SHALL expose `AssetPrice getReferencePrice(Instant timestamp)`. For each supplied instant on or after 2026-07-31T22:00:00Z it SHALL calculate 15.00 × 1.20^(elapsedSeconds / 31,536,000), returning the canonical EVE/USDT trading pair. It SHALL preserve the existing USDT-as-USD index convention without conversion, duplicate constants, separate configuration, cached historical measurements, or dependence on a sampling interval.
|
||||||
|
|
||||||
|
#### Scenario: Public reference price at the start
|
||||||
|
- **WHEN** a consumer requests the reference price for 2026-07-31T22:00:00Z
|
||||||
|
- **THEN** the price is 15 with the canonical EVE base and USDT quote currencies
|
||||||
|
|
||||||
|
#### Scenario: Public reference price after one year
|
||||||
|
- **WHEN** a consumer requests the reference price exactly 31,536,000 seconds after the start
|
||||||
|
- **THEN** the price is 18 with the same canonical trading pair
|
||||||
|
|
||||||
|
#### Scenario: Supplied timestamp determines the price
|
||||||
|
- **WHEN** a consumer requests a valid instant distinct from the clock or latest historical point
|
||||||
|
- **THEN** the returned value is calculated for that instant rather than the current clock or historical sample
|
||||||
|
|
||||||
|
### Requirement: Reference-price requests are independent of sampling and storage
|
||||||
|
A reference-price request SHALL succeed before sampling starts and while sampling is stopped, including when history storage has not been provisioned. It SHALL NOT fetch market prices, access history files, start a worker, or mutate status history. A null timestamp SHALL be rejected with `NullPointerException`; an instant before the reference curve starts SHALL be rejected with `IllegalArgumentException`.
|
||||||
|
|
||||||
|
#### Scenario: Reference price before startup without storage
|
||||||
|
- **WHEN** a constructed service with no provisioned history storage receives a valid reference-price request
|
||||||
|
- **THEN** it returns the calculated value without a ticker lookup, file access, worker startup, or history change
|
||||||
|
|
||||||
|
#### Scenario: Reference price while sampling is stopped
|
||||||
|
- **WHEN** sampling is stopped and a valid reference-price request is made
|
||||||
|
- **THEN** the service returns the calculated value without restarting sampling or reading, writing, or mutating history
|
||||||
|
|
||||||
|
#### Scenario: Reference price during sampling preserves history
|
||||||
|
- **WHEN** reference-price requests are made while the service holds a status-history snapshot
|
||||||
|
- **THEN** the requests do not add or alter any status point or file record
|
||||||
|
|
||||||
|
#### Scenario: Null or pre-start timestamp is rejected
|
||||||
|
- **WHEN** a caller supplies null or an instant before 2026-07-31T22:00:00Z
|
||||||
|
- **THEN** the service rejects it with the respective null or argument exception without side effects
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Component IDs are canonical validated values
|
||||||
|
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
|
||||||
|
|
||||||
|
#### Scenario: Surrounding whitespace is canonicalized
|
||||||
|
- **WHEN** an ID is constructed from ` nenjim.registry.service `
|
||||||
|
- **THEN** its stored value is `nenjim.registry.service`
|
||||||
|
|
||||||
|
#### Scenario: Valid component IDs are accepted
|
||||||
|
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.status.prod` are constructed
|
||||||
|
- **THEN** construction succeeds
|
||||||
|
|
||||||
|
#### Scenario: Invalid component IDs are rejected
|
||||||
|
- **WHEN** an ID is null or contains uppercase letters, underscores, internal whitespace, an empty segment, a leading or trailing dot, or a malformed hyphen
|
||||||
|
- **THEN** construction fails with the specified null or argument exception category and identifies the invalid canonicalized value without exposing secrets
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any process-specific passive administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Status API and Calculation
|
||||||
|
|
||||||
|
- [x] 1.1 Move/rename the API and implementation and move the package-private calculator; verify only these three Evelyn files change packages and `EvelynStatusIndexPoint` stays unchanged.
|
||||||
|
- [x] 1.2 Add documented `AssetPrice getReferencePrice(Instant)` delegating to the existing calculator and canonical pair; verify deterministic start/year/fractional values, invalid timestamps, no ticker/clock/storage access, unchanged history and operation before startup/while stopped.
|
||||||
|
- [x] 1.3 Document the renamed API and reference-price contract; verify the documented curve, units and preserved lifecycle/storage match the code.
|
||||||
|
|
||||||
|
## 2. Hub and Mission Control Integration
|
||||||
|
|
||||||
|
- [x] 2.1 Rename Hub types, fields, factories and component IDs and Mission Control's typed dependencies; compile main/test sources and inspect Process Manager's use of Hub-provided IDs.
|
||||||
|
- [x] 2.2 Document private deployment ID substitutions and update affected versioned examples without changing their selections; verify the example still selects only Registry and Journal and private runtime configuration is untouched.
|
||||||
|
- [x] 2.3 Verify separate Production/Test histories, index calculation, durable append, immutable snapshots and restart behavior with offline temporary storage; review Mission Control chart wiring and retained six-field scaffolding.
|
||||||
|
|
||||||
|
## 3. Integration and Review
|
||||||
|
|
||||||
|
- [x] 3.1 Update the affected existing OpenSpec capabilities from the deltas and strictly validate this change; verify the renamed API, operation and IDs are represented without archiving the change.
|
||||||
|
- [x] 3.2 Run the focused acceptance checks and existing offline verification, compile all source sets, run `git diff --check`, and review the diff for generated-file edits, runtime data/config changes, financial activation and commits.
|
||||||
|
|
||||||
|
## Verification Result
|
||||||
|
|
||||||
|
- Gradle 9.6.1 / JDK 25: `classes testClasses test --offline --console=plain --no-daemon` passed. A temporary writable Gradle home and the existing read-only dependency cache were used in this session.
|
||||||
|
- Five status-service acceptance checks and four existing offline Solana transaction-construction checks passed: 9 tests, 0 failures, 0 errors.
|
||||||
|
- Strict OpenSpec validation passed for this change and its three updated main capabilities.
|
||||||
|
- `git diff --check` passed; generated source, private configuration and runtime data were not edited, the versioned bootstrap example and status-point record are unchanged, and the index calculator differs only in its package.
|
||||||
|
- No Hub or live financial process was run. The change remains active, all eight tasks are complete, and no files were staged or committed.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-04
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation. Raydium already discovers position NFTs and decodes pool/position state but does not execute liquidity decrease. SolanaWallet provides external jup signer validation, signing/submission and SPL transfer with ATA creation; JupiterSwapService confirms exact-input swaps. The Hub has a constructor catalogue and explicit ordered process selection.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Implement the specified pipeline with fresh direct RPC reads and exact eligibility math; leave an offline-verifiable, inactive deployment template.
|
||||||
|
|
||||||
|
**Non-Goals:** No generic strategy, service-container framework, durable pending transaction queue or new currency ValueTags.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Keep the public service a domain marker, mirroring the burner; the reference implementation additionally owns NenjimProcess startup and AutoCloseable shutdown. One scheduled executor uses fixed delay and propagates interruption across step boundaries.
|
||||||
|
- Keep the existing shared Hub graph eager; construct/register liquidity deployment configuration and dedicated passive dependencies only at the selected liquidity entry, as reflected by the Hub/Registry deltas.
|
||||||
|
- Build a dedicated direct Raydium instance when selected so cached pool/position state cannot trigger stale decisions. Resolve currencies through CIS and programs/precision from validated mint accounts.
|
||||||
|
- Finish NFT discovery first, then read a fresh pool state immediately before evaluating each owned positive-liquidity position. Pass that same state to withdrawal minimum-output math. Do not reuse a price across discovery or earlier withdrawals; an unusable pool read stops the remaining withdrawals in that iteration while swap and payout remain independent.
|
||||||
|
- Compare squared Q64.64 values by exact cross multiplication. Decimal factors cancel because pool and position share mints. For reversed ordering compare current raw price to lower-bound raw price times the retained percentage. This avoids display rounding and inversion rounding.
|
||||||
|
- Port the official CLMM integer tick algorithm into a small Raydium math helper, reusing it for exact integer principal amounts. Existing display math remains unchanged.
|
||||||
|
- Execute decrease_liquidity_v2 (legacy and Token-2022) with owner NFT holding, personal/protocol position PDA, vaults, tick arrays, owner destination token accounts and bitmap extension. Reuse an existing owner holding for each pool/reward mint, validating its account's mint, owner, program and initialized state. Only fall back to an existing or idempotently created ATA when no holding exists; failed holdings discovery must not create an extra account. This preserves the existing wallet's single-account-per-mint contract. Request a 600,000 compute-unit limit for the combined operation. Account details stay in the Raydium implementation; a private legacy transaction encoder uses existing address codec, PDA and wallet primitives.
|
||||||
|
- Validate pool and personal-position discriminators, token metadata and owner holding before signing. Apply explicit slippage to integer expected principal. Fail closed on unsupported transfer-affecting Token-2022 extensions rather than silently weakening minimums. Required reward accounts, if present, only collect into this wallet and never enter swaps/payouts.
|
||||||
|
- Parse only conf/evelyn-liquidity.conf with format-version validation, unknown/duplicate key rejection and required keys. Deployment addresses/signer remain placeholders in the ignored local runtime file and tracked example. Existing Hub example stays unchanged.
|
||||||
|
- Use controlled JUnit fixtures explicitly requested by issue #89, including transaction bytes. No test constructs a real signer or submits a financial operation.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Pool moves during discovery or earlier withdrawals] → Refresh price and eligibility for each position. Minimum principal limits protect received principal, not the harvest-distance threshold. Market movement after the final decision remains possible before execution; later iterations reread state.
|
||||||
|
- [Unknown transaction outcome] → Log signature when available, preserve ordinary underlying confirmation; subsequent iterations use current balances/positions as requested.
|
||||||
|
- [Mint extension or program layout changes] → Validate discriminators/layout and fail safely on unsupported extensions; document official source references and exact supported behavior.
|
||||||
|
- [SOL starvation] → Document funding for transaction fees and token-account rent; no hidden token reserves.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Copy/edit the example, bind a dedicated external signer, fund SOL and manually select evelyn.liquidity.prod in the Hub runtime plan. Stop/remove the selection to disable; positions and wallet balances remain the recovery state. Keep the OpenSpec change active for user review; no commit, push or deployment.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Evelyn needs to realize proceeds from manually funded EVE/SyrupUSDC CLMM positions before a falling price converts them back to EVE. Issue #89 adds the smallest automated realization and USDC payout pipeline supporting Evelyn's sustained 100 USD/day objective.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a domain-only EvelynLiquidityService and a Hub-owned sequential process with three independent withdrawal, exact-input swap and payout steps.
|
||||||
|
- Add full Raydium CLMM liquidity decrease with slippage protection and retained position accounts/NFTs.
|
||||||
|
- Add strict versioned deployment configuration and a public placeholder example with both reserves zero.
|
||||||
|
- Lazily construct dedicated liquidity wallet, Jupiter and direct Raydium dependencies only for the selected process.
|
||||||
|
- Verify with controlled offline fixtures and document operation/recovery.
|
||||||
|
- Correct the review findings from commit `7dfd43d`: refresh the pool price for each position after discovery and reuse existing owner token accounts before creating destination ATAs.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `evelyn-liquidity-service`: Distance-based position realization, reserve-aware conversion/payout, configuration and runtime behavior.
|
||||||
|
- `raydium-clmm-liquidity-withdrawal`: Protected full liquidity decrease preserving positions and NFTs.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `nenjim-hub-configuration`: Add the optional process to the known catalogue and defer its dedicated dependencies until selection.
|
||||||
|
- `nenjim-component-registry`: Preserve eager shared composition while registering dedicated liquidity components only for the selected process.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Adds API and reference implementation packages under com.r35157.evelyn.service.liquidity, extends Raydium, and adds one optional Hub constructor. Deployment configuration and signer are required only when explicitly selected. No position creation/refill/closure, reference-price dependency, notifications or persistent service queue. No live financial verification, commits or pushes.
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Realize Evelyn's manually funded EVE/SyrupUSDC concentrated liquidity into USDC payouts using one dedicated wallet and current on-chain state.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Owned positions qualify by their normalized upper boundary
|
||||||
|
The service SHALL discover positions owned by its wallet in its configured CLMM pool and withdraw positive liquidity only when the upper EVE/SyrupUSDC boundary U satisfies U <= P*(1-h/100). P SHALL come from a fresh on-chain pool read for each owned positive-liquidity position after NFT discovery and previous withdrawals have completed. The same state SHALL be used for that position's eligibility decision and withdrawal quote; an earlier position's price SHALL NOT be reused. Both orientations SHALL be normalized, and decisions SHALL use pool precision. Unusable P SHALL skip the remaining withdrawals in that iteration while allowing later steps.
|
||||||
|
|
||||||
|
#### Scenario: Inclusive threshold and upper boundary
|
||||||
|
- **WHEN** P is 15, h is 10 and positions span 13–13.5 and 13.5–14
|
||||||
|
- **THEN** only the first qualifies, including equality
|
||||||
|
|
||||||
|
#### Scenario: Reversed pool ordering
|
||||||
|
- **WHEN** mint A is SyrupUSDC and mint B is EVE
|
||||||
|
- **THEN** the inverted lower raw boundary determines the normalized upper price and the same eligibility rule applies
|
||||||
|
|
||||||
|
#### Scenario: Other or empty positions and falling market
|
||||||
|
- **WHEN** a position belongs to another wallet or pool, has zero liquidity, or its upper boundary exceeds the threshold after a price decline
|
||||||
|
- **THEN** it retains its state and the service neither withdraws it nor creates or refills positions
|
||||||
|
|
||||||
|
#### Scenario: Price declines during discovery or an earlier withdrawal
|
||||||
|
- **WHEN** a position qualifies before discovery or an earlier withdrawal, but its upper boundary exceeds the threshold at the new pool price
|
||||||
|
- **THEN** the service reads that new price before deciding and preserves the position's liquidity in either token orientation
|
||||||
|
|
||||||
|
#### Scenario: Pool read fails between positions
|
||||||
|
- **WHEN** an earlier position was withdrawn but the next current pool price is unavailable or unusable
|
||||||
|
- **THEN** no remaining position is withdrawn in that iteration and swap and payout still run on their independent balances
|
||||||
|
|
||||||
|
### Requirement: Swap and payout use independent current balances
|
||||||
|
The service SHALL read SyrupUSDC at step 2 and USDC at step 3 and operate on the entire respective balance above its nonnegative reserve. Absent or at/below-reserve balances SHALL be skipped. Exact-input swap SHALL use explicit maximum slippage. SPL payout SHALL target the configured wallet owner with existing account-creation support. Amounts SHALL never round upward. EVE and reward tokens SHALL remain untouched by this pipeline.
|
||||||
|
|
||||||
|
#### Scenario: Nonzero reserves
|
||||||
|
- **WHEN** SyrupUSDC balance is 1.01 with reserve 0.01 and USDC balance is 2.5 with reserve 0.5
|
||||||
|
- **THEN** 1.00 SyrupUSDC is swapped and 2.0 USDC is sent
|
||||||
|
|
||||||
|
#### Scenario: Zero reserves and empty accounts
|
||||||
|
- **WHEN** reserves are zero
|
||||||
|
- **THEN** full positive balances are eligible, absent/zero balances are skipped and empty source accounts are not closed
|
||||||
|
|
||||||
|
### Requirement: Failure recovery uses on-chain state
|
||||||
|
Each iteration SHALL attempt withdrawal, swap and payout independently in that order, isolating ordinary per-position failures. Subsequent iterations and restarts SHALL reread positions and balances without a persistent queue, attribution, pending-signature gate, immediate retries or replay. Logs SHALL identify operations, positions, amounts and returned signatures without signer secrets. Interruption SHALL stop work.
|
||||||
|
|
||||||
|
#### Scenario: Independent failures and restart
|
||||||
|
- **WHEN** withdrawal or swap fails or returns an unknown outcome
|
||||||
|
- **THEN** independent later work can proceed and the next iteration or restart acts on current state, skipping completed empty positions
|
||||||
|
|
||||||
|
### Requirement: Configuration is validated before worker start
|
||||||
|
A dedicated version-1 runtime configuration SHALL validate wallet/signer binding, pool type/token identity, address syntax, 0<=h<100, positive fixed delay, nonnegative reserves with mint precision and explicit slippage limits. Currencies, mints and programs SHALL use canonical identity and chain metadata. The public example SHALL have zero reserves and SHALL never be a runtime fallback.
|
||||||
|
|
||||||
|
#### Scenario: Invalid input or missing deployment file
|
||||||
|
- **WHEN** configuration is invalid or only the example exists
|
||||||
|
- **THEN** startup fails before financial work
|
||||||
|
|
||||||
|
### Requirement: Optional process owns a sequential worker
|
||||||
|
The Hub SHALL select evelyn.liquidity.prod through its existing catalogue and Registry, constructing deployment configuration and dedicated signer/swap dependencies only when selected. Existing selections and ordering SHALL be preserved. The domain interface SHALL expose no lifecycle operations. A single worker SHALL run the first and subsequent iterations identically, with a fixed delay after completion and ordinary shutdown interruption.
|
||||||
|
|
||||||
|
#### Scenario: Optional process is omitted
|
||||||
|
- **WHEN** the Hub plan omits evelyn.liquidity.prod
|
||||||
|
- **THEN** no liquidity configuration or dedicated signer is loaded and the checked-in Hub example activates no financial operation
|
||||||
|
|
||||||
|
#### Scenario: Slow iteration and shutdown
|
||||||
|
- **WHEN** an iteration exceeds the configured interval or shutdown interrupts it
|
||||||
|
- **THEN** no iterations overlap and interruption prevents further steps
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: NenjimHub owns composition and runtime startup
|
||||||
|
`NenjimHub` SHALL remain the global runtime owner exposing only `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither component nor process and SHALL not register itself. Its only accepted startup attempt SHALL validate the complete fixed-path Hub configuration before starting anything, construct and start Registry first, register Registry as `nenjim.registry.service`, record it, construct and register the existing shared passive graph, then lazily construct, register, start, and record only the remaining configured processes in exact order beginning with Journal. The selected liquidity process SHALL additionally compose its dedicated passive dependencies lazily at its configured entry. Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub. Registration SHALL never imply startup. The Hub SHALL preserve the online point and blocking wait only after all configured processes succeed, without retry, rollback, dependency inference, or reordering.
|
||||||
|
|
||||||
|
#### Scenario: Registry starts before passive graph construction
|
||||||
|
- **WHEN** the Hub has validated a complete startup plan and performs its first startup attempt
|
||||||
|
- **THEN** it starts, registers, and records Registry first before constructing the passive component graph or any selected non-Registry process
|
||||||
|
|
||||||
|
#### Scenario: Journal follows passive composition but starts second
|
||||||
|
- **WHEN** Registry bootstrap and passive graph construction have succeeded
|
||||||
|
- **THEN** the Hub constructs, registers, starts, and records Journal before any later configured process, so Journal remains the second successful process
|
||||||
|
|
||||||
|
#### Scenario: Registry is process-discoverable
|
||||||
|
- **WHEN** the Registry registration has completed
|
||||||
|
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||||
|
|
||||||
|
#### Scenario: Journal is process-discoverable
|
||||||
|
- **WHEN** the configured Journal construction and registration have completed
|
||||||
|
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||||
|
|
||||||
|
#### Scenario: Selected JavaFX runtime is process-discoverable
|
||||||
|
- **WHEN** `nenjim.javafx-runtime.service` is selected and its registration has completed
|
||||||
|
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under that ID
|
||||||
|
|
||||||
|
#### Scenario: Selected JavaFX applications share the runtime
|
||||||
|
- **WHEN** JavaFX runtime precedes selected Mission Control or Process Manager entries
|
||||||
|
- **THEN** each selected application retains the same already-constructed `JavaFxRuntimeService` instance registered by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Selected Process Manager is process-discoverable
|
||||||
|
- **WHEN** `nenjim.process-manager.default` is selected after JavaFX runtime and its construction and registration complete
|
||||||
|
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess`
|
||||||
|
|
||||||
|
#### Scenario: Required passive bindings are registered
|
||||||
|
- **WHEN** Hub passive composition completes
|
||||||
|
- **THEN** all current passive component IDs and implementation choices from issue #76 are present exactly as configured, while process IDs other than Registry remain absent until selected
|
||||||
|
|
||||||
|
#### Scenario: Configured startup subset preserves order
|
||||||
|
- **WHEN** Hub startup completes successfully
|
||||||
|
- **THEN** successful process IDs exactly equal the validated `[AUTO_START]` entries in their declared order
|
||||||
|
|
||||||
|
#### Scenario: Omitted processes do not enter the Registry
|
||||||
|
- **WHEN** a modern process ID is omitted from `[AUTO_START]`
|
||||||
|
- **THEN** that process is neither constructed, registered, started, nor recorded even though its implementation is known to the Hub
|
||||||
|
|
||||||
|
#### Scenario: Journal failure is fail-fast
|
||||||
|
- **WHEN** Journal construction, registration, or process startup throws
|
||||||
|
- **THEN** Registry remains the only recorded process, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
|
#### Scenario: JavaFX runtime failure is fail-fast
|
||||||
|
- **WHEN** selected JavaFX runtime construction, registration, or startup throws
|
||||||
|
- **THEN** its ID is not recorded, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
|
#### Scenario: Hub start is not retried
|
||||||
|
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||||
|
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||||
|
|
||||||
|
#### Scenario: Startup failure is fail-fast
|
||||||
|
- **WHEN** any passive composition, selected construction, registration, or process startup operation throws
|
||||||
|
- **THEN** the exception propagates, no later process is constructed or started, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||||
|
|
||||||
|
#### Scenario: Dedicated liquidity bindings use the liquidity signer
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is selected and its construction succeeds
|
||||||
|
- **THEN** `solana.wallet.evelyn-liquidity-prod`, `jupiter.swap.evelyn-liquidity-prod` and `raydium.service.evelyn-liquidity-prod` are registered, the swap service uses the dedicated liquidity wallet and Raydium uses direct chain reads
|
||||||
|
|
||||||
|
#### Scenario: Liquidity service has orthogonal domain and lifecycle views
|
||||||
|
- **WHEN** the selected liquidity process is registered
|
||||||
|
- **THEN** the same object resolves as `EvelynLiquidityService` and `NenjimProcess`, while the domain interface exposes no lifecycle operations
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Registry bootstraps eager passive composition
|
||||||
|
After validating the entire plan, the Hub SHALL construct the configured Registry subsystem, start Registry, register its service and process object as `nenjim.registry.service`, and record that ID only after successful startup. The Hub SHALL then eagerly construct and register the existing shared passive component graph. Dedicated liquidity deployment configuration, wallet signer, Jupiter swap service and direct Raydium service SHALL instead be constructed and registered only when the configured `evelyn.liquidity.prod` entry is reached. This passive phase SHALL not construct any canonical `NenjimProcess`, start background workers, initialize JavaFX, add startup IDs, infer process dependencies, or implicitly create an omitted process. Plain internal dependencies that are not `NenjimComponent` objects MAY be constructed as part of passive composition.
|
||||||
|
|
||||||
|
#### Scenario: Registry surrounds passive composition
|
||||||
|
- **WHEN** a valid Hub startup begins
|
||||||
|
- **THEN** Registry starts, is registered, and is recorded before the existing shared passive component graph is constructed and registered
|
||||||
|
|
||||||
|
#### Scenario: Passive composition completes
|
||||||
|
- **WHEN** Registry bootstrap succeeds
|
||||||
|
- **THEN** all existing shared non-process building blocks, adapters, and services are constructed and registered without starting a process
|
||||||
|
|
||||||
|
#### Scenario: Passive construction fails
|
||||||
|
- **WHEN** any passive component cannot be constructed or registered
|
||||||
|
- **THEN** startup stops immediately with Registry remaining recorded and no configured process after Registry constructed or started
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is omitted
|
||||||
|
- **WHEN** the plan omits `evelyn.liquidity.prod`
|
||||||
|
- **THEN** its deployment configuration, dedicated signer/wallet, Jupiter service and direct Raydium service are neither loaded nor constructed nor registered
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is explicitly selected
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||||
|
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide a narrowly scoped protected Raydium CLMM full liquidity decrease using the owner's existing Solana signing and submission primitives.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Full withdrawal preserves the reusable position
|
||||||
|
The operation SHALL validate the NFT owner, pool and positive current liquidity, decrease that complete liquidity into the same wallet, collect pool-token fees and preserve the position account, NFT and NFT token account. For each pool or reward mint it SHALL reuse an existing owner token holding, including a non-associated account, after validating its mint, owner, program and initialized state. It SHALL use or create an ATA only when no owner holding exists and SHALL NOT fall back to account creation when holdings discovery fails. Transaction/account details SHALL remain behind Raydium. It SHALL submit once and use existing confirmation behavior without close, burn or service-level retry.
|
||||||
|
|
||||||
|
#### Scenario: Full protected withdrawal
|
||||||
|
- **WHEN** a wallet withdraws its position's complete liquidity
|
||||||
|
- **THEN** the transaction names the expected wallet, pool, liquidity and nonzero minimum output protection and contains no close-position, NFT burn or token-account close instruction
|
||||||
|
|
||||||
|
#### Scenario: Wrong owner or changed position
|
||||||
|
- **WHEN** ownership, pool or current position state is inconsistent with the requested withdrawal
|
||||||
|
- **THEN** the operation fails before signing
|
||||||
|
|
||||||
|
#### Scenario: Existing non-associated token accounts
|
||||||
|
- **WHEN** the wallet already has a pool-token or reward holding outside its ATA under either supported token program
|
||||||
|
- **THEN** the withdrawal targets that validated account and creates no additional account for that mint, preserving subsequent wallet balance reads
|
||||||
|
|
||||||
|
#### Scenario: Existing holding cannot be trusted
|
||||||
|
- **WHEN** holdings discovery fails or the selected account has a wrong mint, owner, token program or initialized state
|
||||||
|
- **THEN** withdrawal fails before signing without creating a replacement token account
|
||||||
|
|
||||||
|
### Requirement: Minimum output derives from precise state and explicit slippage
|
||||||
|
Withdrawal minimums SHALL derive from current Q64.64 pool state, exact CLMM tick/liquidity math and configured slippage with downward raw-token rounding. Both minimums SHALL NOT be unconditionally zero. Required legacy/Token-2022 accounts, tick arrays and bitmap extension SHALL follow the official decrease instruction. Unsupported token extensions SHALL fail safely before signing.
|
||||||
|
|
||||||
|
#### Scenario: One-sided principal and fees
|
||||||
|
- **WHEN** the range is fully below the normalized current price
|
||||||
|
- **THEN** SyrupUSDC principal is protected, any EVE fees return to the wallet and all received EVE remains there
|
||||||
|
|
||||||
|
#### Scenario: Precision and tiny position
|
||||||
|
- **WHEN** expected principal is too small to provide a positive raw-unit minimum
|
||||||
|
- **THEN** the operation fails rather than submitting an unchecked withdrawal
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Protected Raydium withdrawal
|
||||||
|
|
||||||
|
- [x] 1.1 Add exact CLMM tick, eligibility and raw principal math; verify official boundary fixtures, equality and inversion.
|
||||||
|
- [x] 1.2 Add full decrease operation with owner/account validation, slippage and retained NFT; verify captured transaction bytes with offline fixtures and document API/source references.
|
||||||
|
|
||||||
|
## 2. Liquidity service and deployment configuration
|
||||||
|
|
||||||
|
- [x] 2.1 Add versioned strict configuration and zero-reserve public template; verify invalid ranges, addresses, precision, duplicate/unknown keys and missing-file behavior.
|
||||||
|
- [x] 2.2 Implement independent withdrawal/swap/payout steps and sequential interruptible lifecycle; verify reserves, failure/restart recovery, empty/foreign positions, price decline, no overlap and shutdown using controlled dependencies.
|
||||||
|
- [x] 2.3 Document configuration, dedicated signer/SOL requirements and pipeline/recovery behavior; inspect documentation against issue #89.
|
||||||
|
|
||||||
|
## 3. Hub integration and overall validation
|
||||||
|
|
||||||
|
- [x] 3.1 Lazily register the optional process and dedicated wallet/Jupiter/direct Raydium dependencies; verify optional selection preserves existing startup plan and checked-in example inactivity.
|
||||||
|
- [x] 3.2 Compile main/verification sources, run affected offline tests, validate OpenSpec strictly and run git diff --check; record results without live financial operations, commits or pushes.
|
||||||
|
|
||||||
|
## 4. Review corrections for commit 7dfd43d
|
||||||
|
|
||||||
|
- [x] 4.1 Refresh pool state per position after discovery and previous withdrawals; verify price declines in both orientations, quote consistency and independent later steps after a pool-read failure.
|
||||||
|
- [x] 4.2 Reuse validated existing pool/reward token holdings before creating ATAs; verify both token programs, invalid accounts, failed holdings reads and absence of duplicate-account creation.
|
||||||
|
- [x] 4.3 Update operation documentation, compile and run all offline tests, validate OpenSpec strictly and check the diff without live financial operations, commits or pushes.
|
||||||
|
|
||||||
|
## Verification result (2026-10-04)
|
||||||
|
|
||||||
|
- Main and verification compilation passed with JavaFX JDK 25 and Gradle 9.6.1.
|
||||||
|
- All 34 repository JUnit tests passed (25 liquidity/math/withdrawal/Hub tests and 9 existing tests); no failures, errors or skips.
|
||||||
|
- Strict OpenSpec change validation passed; git diff --check and new-file whitespace checks passed.
|
||||||
|
- Public mainnet inspection was read-only. All financial operation verification used controlled offline dependencies; no live withdrawal, swap, transfer, signer invocation, commit or push.
|
||||||
|
- Runtime configuration contains deployment placeholders and is ignored by Git. Both example reserves are zero; the Hub runtime selection and checked-in Hub example are unchanged.
|
||||||
|
|
||||||
|
## Review correction verification (2026-10-04)
|
||||||
|
|
||||||
|
- Eight new regression tests reproduced the findings before their respective fixes and passed afterward: four cover per-position price freshness, quote consistency and independent later steps; four cover existing pool/reward accounts, invalid accounts and failed holdings reads.
|
||||||
|
- Main/verification compilation and all 42 repository JUnit tests passed with no failures, errors or skips.
|
||||||
|
- Strict OpenSpec validation and git diff --check passed. The active change has 10/10 completed tasks.
|
||||||
|
- Verification used controlled offline dependencies only. No live financial operation, real signer invocation, commit, push or deployment was performed.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-05
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation and issue #90 for the agreed operating values. The existing burner already supports ordered SOL/SPL inputs, downward precision rounding and Jupiter exact-input swaps. Its reserve list is the authoritative accepted-input configuration. The constructor's scalar USDC cap and input-price lookup are the only blockers to selecting SyrupUSDC without additional Ticker observations.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Keep selection independent of input prices while rejecting incomplete or ambiguous limits before any worker starts. Change the amount policy without changing transaction outcomes, recovery or burning.
|
||||||
|
|
||||||
|
**Non-Goals:** Introduce another input model, a general configuration subsystem, or change how the latest EVE/USDT observation is selected.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Accept maxima as a second `List<MoneyAmount>` and copy their validated values into an immutable UUID-keyed map. The reserve list keeps its original defensive copy and order. UUID lookup permits the maxima list to arrive in any order; matching positions or metadata would create avoidable ambiguity. Existing immutable `MoneyAmount` values suffice without new types or ValueTags.
|
||||||
|
2. Validate maxima entries, amounts, currencies and UUIDs, canonical CIS identity, positivity and uniqueness, then require exact agreement with the accepted reserve UUIDs. Keep existing reserve and Solana-representation validation unchanged. Empty lists match and retain direct-EVE handling. A compatibility scalar constructor would preserve obsolete price semantics, so it is not retained.
|
||||||
|
3. Select `min(balance - reserve, maximum.amount())` only for positive spendable balance, then use the existing downward mint-precision rounding and zero skip. Remove the USDC canonical field and all input price construction/queries. Keep EVE/USDT canonical construction and fresh-price enforcement intact.
|
||||||
|
4. Add the agreed two reserve and maximum entries in Hub construction. Retain every other argument and the existing wallet/Jupiter bindings. Jupiter continues to choose pool and route; no Raydium transaction path is added.
|
||||||
|
5. Compile through the integrated Gradle/Detag build, and verify the compiled implementation with temporary offline controlled dependencies outside the repository. Do not add a permanent test artifact. Verify amount boundaries, UUID matching, invalid configuration, rounding, and existing failure/pending-burn behavior without network or real transaction submission.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Token limits vary in USD value → document that 0.2 SyrupUSDC is a token amount and retain explicit small limits.
|
||||||
|
- Constructor signature changes → update the single repository call site and compile all relevant sources.
|
||||||
|
- USDT can defer SyrupUSDC indefinitely while spendable → preserve the agreed priority and one-attempt rule rather than changing scheduling.
|
||||||
|
- Unknown transaction outcomes must not be retried → leave outcome handlers unchanged and verify suspension/pending behavior offline.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Implementation is local and reviewable; deployment is a separate task. A later deployment replaces the application with the compiled change while preserving wallet, startup configuration and existing Ticker history. External callers must replace a scalar cap with exactly one token-denominated maximum per accepted reserve UUID. Rollback restores the prior implementation and USDT-only Hub configuration after reconciling any unknown transaction outcomes.
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The production Evelyn IOU burner accepts only USDT, and its common USDC-equivalent ceiling needs input prices for other tokens. Issue #90 requires deposited SyrupUSDC to fund the same Jupiter buyback and burn pipeline using a small, explicit token-denominated limit.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING**: Replace the constructor's scalar `maximumSwapAmountInUSDC` with `List<MoneyAmount> inputCurrencyMaximumSwapAmounts`, validated and matched to accepted inputs by canonical currency UUID.
|
||||||
|
- Calculate each exact input amount from spendable balance and that currency's maximum, rounding downward to mint precision without querying an input/USDC price.
|
||||||
|
- Keep USDT first with reserve 0.1 and maximum 0.25; add SyrupUSDC second with reserve 0.095 and maximum 0.2 in `constructEvelynIOUBurnerProdProcess()`.
|
||||||
|
- Preserve Jupiter routing, EVE/USDT freshness before burning, all transaction/recovery behavior, and Hub-owned lifecycle.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: Token-denominated maxima, UUID-matched configuration validation, price-independent input selection, and production SyrupUSDC support.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Changes are limited to the burner reference constructor/selection logic, its Hub construction, directly affected documentation and OpenSpec requirements. Verification compiles normal sources and uses controlled offline dependencies without live financial transactions. No Ticker sources, API behavior, history paths, ValueTags, direct Raydium swaps, deployment, or unrelated refactoring are included.
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Ordered AssetAZ input configuration
|
||||||
|
The authoritative constructor SHALL accept a non-null ordered `List<MoneyAmount>` named `inputCurrencyMinimumReserves`, defensively copy it without changing order, and interpret inclusion as acceptance, list position as priority, each `currencyType().id()` as the authoritative AssetAZ identity, and each `amount()` as the minimum balance that must remain. It SHALL ignore supplied currency name and symbol metadata by resolving each UUID through the injected Currency Identity Service and using the canonical returned currency for processing. The constructor SHALL also accept a non-null `List<MoneyAmount>` named `inputCurrencyMaximumSwapAmounts`, defensively copy its values, and match each maximum to an accepted input by canonical currency UUID, independently of maximum-list order or supplied metadata. Each maximum SHALL be expressed in that same currency's own units. Empty reserves and maxima lists SHALL be valid and SHALL still permit directly deposited EVE to be burned.
|
||||||
|
|
||||||
|
#### Scenario: Stale configured metadata
|
||||||
|
- **WHEN** a configured `MoneyAmount` carries a known UUID but stale name or symbol metadata
|
||||||
|
- **THEN** the service processes the current canonical currency resolved for that UUID
|
||||||
|
|
||||||
|
#### Scenario: Priority is preserved
|
||||||
|
- **WHEN** multiple configured entries have spendable balances and usable Solana data
|
||||||
|
- **THEN** the earliest entry in the defensively copied list is the only input selected in that iteration
|
||||||
|
|
||||||
|
#### Scenario: Empty input list
|
||||||
|
- **WHEN** no buyback inputs are configured but the wallet contains EVE
|
||||||
|
- **THEN** the service skips input selection and still evaluates the complete EVE balance for burning
|
||||||
|
|
||||||
|
#### Scenario: Maximum list order is independent
|
||||||
|
- **WHEN** maxima are supplied in a different order from the reserve list or carry stale display metadata for known UUIDs
|
||||||
|
- **THEN** each input receives the maximum for its UUID and reserve-list priority is preserved
|
||||||
|
|
||||||
|
#### Scenario: Caller changes its configuration lists
|
||||||
|
- **WHEN** a caller changes either supplied list after construction
|
||||||
|
- **THEN** the accepted currencies, their priority, reserves and maxima remain as validated during construction
|
||||||
|
|
||||||
|
### Requirement: Complete constructor validation
|
||||||
|
Construction SHALL require all injected services, both input configuration lists, and durations to be non-null; every list entry, entry amount, entry currency, and currency UUID to be non-null; each reserve to be zero or greater; each maximum to be strictly positive; each UUID to be known to the injected Currency Identity Service; UUIDs to be unique within each list; the maximum UUID set to exactly equal the accepted reserve UUID set; and EVE not to be an input currency. It SHALL require slippage to be from 0 through 10000 basis points inclusive, `maximumPriceAge` to be greater than zero milliseconds, and both finalization timeout and iteration interval to be positive and non-zero.
|
||||||
|
|
||||||
|
Every configured non-SOL UUID SHALL resolve through reverse CIS lookup to exactly one external reference in the `solana-mint` namespace, and that reference's external ID SHALL be non-null and non-blank. Construction SHALL fail for a missing, ambiguous, null, or blank non-SOL mint representation. These burner-specific rules SHALL NOT change the general-purpose `MoneyAmount` contract.
|
||||||
|
|
||||||
|
#### Scenario: Valid configuration
|
||||||
|
- **WHEN** every dependency and value is valid, reserves are non-negative, every accepted input has exactly one positive maximum, UUIDs are distinct and known within each list, EVE is absent, and every non-SOL input has one non-blank Solana mint reference
|
||||||
|
- **THEN** construction succeeds and preserves the configured priority order
|
||||||
|
|
||||||
|
#### Scenario: Invalid reserve or operating limit
|
||||||
|
- **WHEN** a reserve is negative or null, a token maximum is null or non-positive, slippage is outside its inclusive range, maximum price age is non-positive, or either duration is null or non-positive
|
||||||
|
- **THEN** construction fails before a worker or transaction is created
|
||||||
|
|
||||||
|
#### Scenario: Invalid input identity
|
||||||
|
- **WHEN** an entry or its currency or UUID is null, a UUID is unknown or duplicated, or the EVE UUID is configured as input
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the invalid configuration
|
||||||
|
|
||||||
|
#### Scenario: Non-SOL representation is not unique and usable
|
||||||
|
- **WHEN** a configured non-SOL currency has zero or multiple `solana-mint` references or its sole external ID is null or blank
|
||||||
|
- **THEN** construction fails without adding mint or token-program selection to Evelyn's configuration model
|
||||||
|
|
||||||
|
#### Scenario: Maximum configuration is incomplete or ambiguous
|
||||||
|
- **WHEN** a maximum is missing for an accepted input, duplicated by UUID, or supplied for an unaccepted currency
|
||||||
|
- **THEN** construction fails before a worker or transaction is created
|
||||||
|
|
||||||
|
#### Scenario: Invalid maximum identity or value
|
||||||
|
- **WHEN** a maximum entry, amount, currency or UUID is null, or its UUID is unknown or resolves to an invalid canonical identity
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the invalid maximum configuration
|
||||||
|
|
||||||
|
### Requirement: Fixed Evelyn domain values and internally resolved integration values
|
||||||
|
The service SHALL fix the EVE mint as `meveYG2iXYSkgSUn1T1uxcthH1EGMZdRHGgCntXZA3Y`, construct the EVE/USDT notification-price pair from canonical CIS resolutions of the stable EVE and USDT UUIDs, and use fixed notification wording. Native SOL SHALL be identified only by the stable SOL UUID; when sent to Jupiter, its input mint SHALL be the canonical CIS Solana-mint reference `So11111111111111111111111111111111111111112`. Mint addresses, token programs, price pairs, notification wording, and a separate SOL reserve SHALL NOT be constructor configuration.
|
||||||
|
|
||||||
|
#### Scenario: Native SOL configuration
|
||||||
|
- **WHEN** a configured input UUID is the stable SOL UUID
|
||||||
|
- **THEN** the service reads the native wallet balance and later supplies Jupiter with the CIS-resolved canonical SOL/WSOL mint rather than treating SOL as an SPL-token holding
|
||||||
|
|
||||||
|
#### Scenario: Fixed EVE behavior
|
||||||
|
- **WHEN** the service evaluates a burn and notification
|
||||||
|
- **THEN** it uses the fixed EVE mint and the canonical EVE/USDT pair without accepting replacements through construction
|
||||||
|
|
||||||
|
### Requirement: Prioritized input inspection with internal Solana translation
|
||||||
|
Unless swaps are suspended, an iteration SHALL inspect configured input entries in order until it finds the first usable candidate. For every entry it SHALL resolve the configured UUID to its canonical currency. Native SOL SHALL use `getSolanaBalance().amount()` and nine-decimal precision. Every other input SHALL use its unique CIS-resolved Solana mint; the service SHALL inspect the mint account owner to detect the original SPL Token Program or Token-2022, use that program for wallet balance lookup, and obtain precision from Solana mint metadata. A missing SPL-token account SHALL equal zero balance.
|
||||||
|
|
||||||
|
For each entry the service SHALL subtract its configured reserve and skip non-positive spendable balance. Missing or unusable balance, mint account, supported program, or mint metadata SHALL produce a warning and allow later configured entries to be examined. Interruption SHALL preserve the thread interrupt state and end processing rather than becoming a skipped candidate. Failure to select an input SHALL NOT prevent the later complete-EVE burn evaluation.
|
||||||
|
|
||||||
|
#### Scenario: Missing token account
|
||||||
|
- **WHEN** a configured non-SOL mint is valid but the wallet has no token account under its detected program
|
||||||
|
- **THEN** that input is treated as a zero balance and later inputs are considered
|
||||||
|
|
||||||
|
#### Scenario: Missing or unsupported mint account
|
||||||
|
- **WHEN** a configured token's mint account is absent or owned by an unsupported program
|
||||||
|
- **THEN** the service warns, skips that input, considers later priorities, and still reaches the EVE-balance step
|
||||||
|
|
||||||
|
#### Scenario: Candidate read is interrupted
|
||||||
|
- **WHEN** wallet, CIS-adjacent processing, or Solana access is interrupted while inspecting an input
|
||||||
|
- **THEN** the service preserves interruption and ends processing without treating the input as an ordinary failure
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Exact token-capped amount and per-currency reserve
|
||||||
|
The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and SHALL never exceed either bound through rounding. Input selection SHALL require no input/USDC Ticker observation. Maxima SHALL limit buybacks only; EVE burns SHALL remain uncapped. Native SOL reserves SHALL use observed balance without estimating future fees.
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC maximum is lower than spendable balance
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, and its maximum is 0.2
|
||||||
|
- **THEN** 0.2 SyrupUSDC is selected without requesting a SyrupUSDC/USDC observation
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC spendable balance is lower than the maximum
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.245, its reserve is 0.095, and its maximum is 0.2
|
||||||
|
- **THEN** 0.150 SyrupUSDC is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Reserve consumes the balance
|
||||||
|
- **WHEN** the current balance is equal to or below that entry's minimum reserve
|
||||||
|
- **THEN** no amount of that currency is intentionally swapped
|
||||||
|
|
||||||
|
#### Scenario: USDT maximum preserves the production bound
|
||||||
|
- **WHEN** USDT balance is 0.35, its reserve is 0.1, and its maximum is 0.25
|
||||||
|
- **THEN** 0.25 USDT is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Precision rounding never increases the spend
|
||||||
|
- **WHEN** the lesser of spendable balance and maximum is 0.1509999 and the mint supports six decimals
|
||||||
|
- **THEN** the service selects 0.150999, and if downward rounding instead produces zero the input is skipped
|
||||||
|
|
||||||
|
#### Scenario: Native transaction fees
|
||||||
|
- **WHEN** native SOL is selected with a configured reserve
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
## REMOVED Requirements
|
||||||
|
|
||||||
|
### Requirement: Fresh canonical input prices
|
||||||
|
**Reason**: Buyback limits are expressed in each accepted input's own units, so input/USDC observations are no longer needed.
|
||||||
|
**Migration**: Configure exactly one token-denominated maximum per accepted input; keep the existing fresh EVE/USDT observation before burning.
|
||||||
|
|
||||||
|
### Requirement: Exact USDC-capped amount and per-currency reserve
|
||||||
|
**Reason**: Replaced by exact token-capped amount and per-currency reserve.
|
||||||
|
**Migration**: Replace the scalar USDC-equivalent constructor maximum with the UUID-matched per-currency MoneyAmount list.
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Token-denominated constructor and selection
|
||||||
|
|
||||||
|
- [x] 1.1 Replace the scalar maximum with an immutable UUID-matched configuration, preserve reserve validation, and verify reordered maxima, defensive copying, empty lists and invalid maximum configurations with offline controlled dependencies.
|
||||||
|
- [x] 1.2 Remove input/USDC resolution and pricing, select the lesser spendable/token-maximum amount with existing downward precision rounding, and verify the SyrupUSDC balance examples, reserve boundary and zero-rounding skip offline.
|
||||||
|
- [x] 1.3 Update constructor and domain-service JavaDoc for token limits and EVE-only price freshness; verify no obsolete input-price or scalar-limit contract remains in current source documentation.
|
||||||
|
|
||||||
|
## 2. Production configuration and documentation
|
||||||
|
|
||||||
|
- [x] 2.1 Configure USDT reserve 0.1/maximum 0.25 first and SyrupUSDC reserve 0.095/maximum 0.2 second in Hub construction; verify exact values, unchanged remaining arguments and successful Gradle compilation.
|
||||||
|
- [x] 2.2 Document the production limits, Jupiter routing, unchanged latest-price API and direct-EVE behavior; verify documentation matches the source configuration and delta requirements.
|
||||||
|
|
||||||
|
## 3. Integration verification and OpenSpec completion
|
||||||
|
|
||||||
|
- [x] 3.1 Verify one-attempt priority, confirmed/failed/unknown swap handling, pending FINALIZED burns, complete EVE burning and stale/missing EVE observations offline without financial transactions; record results and limitations in the change.
|
||||||
|
- [x] 3.2 Validate the OpenSpec change, perform diff/whitespace checks, synchronize directly affected main requirements and verify the change is ready for archive with all implementation tasks and requirements complete.
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
## Completed checks
|
||||||
|
|
||||||
|
- `openspec validate 90-add-syrupusdc-burner-input-with-per-currency-limits --strict`: passed.
|
||||||
|
- `openspec validate evelyn-iou-burner-service --type spec --strict`: passed, with informational long-description hints for existing/modified requirements.
|
||||||
|
- `git diff --check`: passed.
|
||||||
|
- Main-spec merge comparison: all four modified requirements and both added requirements match the delta, both removed requirements are absent, and unaffected requirements are preserved.
|
||||||
|
- Source comparison against HEAD confirms that lifecycle and iteration sequencing, swap execution and outcome handling, complete EVE reload/burn, pending burn recovery, FINALIZED waiting, notification behavior, fresh-price validation, Solana precision resolution and reserve validation are unchanged byte for byte.
|
||||||
|
- Current source documentation contains no obsolete scalar USDC limit, USDC canonical field, input-price conversion or fresh input/USDC requirement. The production values and priority match issue #90, including SyrupUSDC reserve 0.095.
|
||||||
|
|
||||||
|
## Compilation and controlled checks
|
||||||
|
|
||||||
|
The parent agent compiled the updated main sources and existing test sources successfully with the normal integrated Gradle/Detag workflow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: `BUILD SUCCESSFUL in 13s`, four tasks executed. No permanent automated tests were added.
|
||||||
|
|
||||||
|
The temporary harness at `/tmp/evelyn-issue90/BurnerVerification.java` was compiled and executed with the same JDK against the newly built classes. Result: **PASS: 52 focused offline assertions**. These cover:
|
||||||
|
|
||||||
|
- SyrupUSDC balances 0.5 → 0.2 and 0.245 → 0.150, at/below 0.095 → skipped.
|
||||||
|
- USDT balance 0.35 → 0.25, with USDT first even when maxima are reversed.
|
||||||
|
- Downward six-decimal rounding and positive amounts that round to zero.
|
||||||
|
- Defensive configuration copying, empty lists and ten invalid-maxima configurations, including missing, duplicate, extra, non-positive, null and unknown values/identities.
|
||||||
|
- No input/USDC Ticker observation, and both missing and stale EVE/USDT observations postponing burns while still permitting input selection.
|
||||||
|
- Complete direct-EVE burning with empty input lists, FINALIZED confirmation, unchanged timeout and one proof notification using the burn signature.
|
||||||
|
|
||||||
|
All financial dependencies and notification delivery were controlled. No worker, live transaction or external notification was started. Swap/burn failure and pending recovery paths were verified by exact source comparison against HEAD; the harness does not claim exhaustive runtime coverage of those unchanged branches.
|
||||||
|
|
||||||
|
Main requirements are synchronized and all implementation tasks are complete. No deployment, push, Gitea write or live financial transaction has been performed.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-05
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
+69
@@ -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
|
||||||
|
|
||||||
+59
@@ -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
|
||||||
|
|
||||||
+33
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-06
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md and issue #92. The constructor currently owns ordered reserve configuration and immutable UUID-indexed maxima. Candidate selection already rounds down before invoking Jupiter; pending recovery precedes selection.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Extend existing token amount policy safely with a lower bound on actual attempted inputs.
|
||||||
|
|
||||||
|
**Non-Goals:** Introduce a configuration hierarchy, compatibility bypass, precision rounding for minima, or change execution/recovery behavior.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Add required minima between reserves and maxima. Reuse one private positive per-UUID amount validator for minima and maxima, copy them to immutable maps with canonical CurrencyType values, and reject minimum > maximum after both maps exist. Reserve order alone retains priority. A duplicated independent validator would unnecessarily duplicate the existing identity/set rules.
|
||||||
|
2. Compare the unchanged rounded candidate with its minimum using `compareTo`, preserving numeric equality across decimal scales. A pre-round comparison would permit attempted inputs below minimum; rounding upward or reserve top-up would violate existing spend bounds. Minima need not be representable at mint precision: they remain exact lower bounds, and an unachievable minimum simply makes that candidate ineligible.
|
||||||
|
3. Make production minima explicit MoneyAmount values in the existing Hub factory and update the single current call site. No old-signature overload bypasses the required policy.
|
||||||
|
4. Keep offline verification temporary, covering issue examples, precision/scale edges, config validation/copying, direct EVE and the prior recovery harness with only its constructor adapted.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Incorrect UUID matching or list reordering → use canonical UUID maps and verify stale display metadata/reordered lists.
|
||||||
|
- Precision makes a configured minimum unachievable → skip the rounded candidate; never promote amount.
|
||||||
|
- Constructor signature changes → update the only current construction site and compile all existing main/test sources.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Implementation adds explicit production configuration; deployment remains a separate task. No persistent data, ticker history or ValueTag migration is required.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Issue #92 prevents unsuitable tiny buybacks above the reserve from blocking useful later inputs. Token-denominated minima let Evelyn skip those candidates while preserving reserve safety, priority and existing recovery.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING** Add a required `List<MoneyAmount>` of per-currency minimum swap inputs to the reference constructor beside reserves and maxima; match canonical UUIDs and copy configuration defensively.
|
||||||
|
- Reject missing, extra, unknown, duplicate or null minimum identities/values, non-positive minima and minima above their matched maxima.
|
||||||
|
- Compare the actual downward-rounded candidate with its minimum. Skip smaller candidates and continue input inspection; accept equality without spending reserves or rounding upward.
|
||||||
|
- Configure production USDT minimum 0.05 and SyrupUSDC minimum 0.045, with current reserve/maxima values and USDT priority retained.
|
||||||
|
- Update directly affected documentation/specs and perform focused offline verification without permanent tests or live transactions.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: Per-input minimum configuration, validation and qualification after precision rounding.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The burner reference constructor and selection, the Hub production call site, domain/constructor JavaDoc and Evelyn configuration documentation change. Jupiter, Ticker, token discovery, lifecycle, pending swap/burn recovery, notification and ValueTags remain unchanged. Implementation and deployment remain separate.
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Ordered AssetAZ input configuration
|
||||||
|
The authoritative constructor SHALL accept a non-null ordered `List<MoneyAmount>` named `inputCurrencyMinimumReserves`, defensively copy it without changing order, and interpret inclusion as acceptance, list position as priority, each `currencyType().id()` as the authoritative AssetAZ identity, and each `amount()` as the minimum balance that must remain. It SHALL ignore supplied currency name and symbol metadata by resolving each UUID through the injected Currency Identity Service and using the canonical returned currency for processing. The constructor SHALL also accept a non-null `List<MoneyAmount>` named `inputCurrencyMaximumSwapAmounts`, defensively copy its values, and match each maximum to an accepted input by canonical currency UUID, independently of maximum-list order or supplied metadata. Each maximum SHALL be expressed in that same currency's own units. The constructor SHALL additionally accept `inputCurrencyMinimumSwapAmounts`, defensively copy its values and match strictly positive token-denominated minima by canonical UUID independently of order/name/symbol. Empty reserves, minima and maxima lists SHALL be valid and SHALL still permit directly deposited EVE to be burned.
|
||||||
|
|
||||||
|
#### Scenario: Stale configured metadata
|
||||||
|
- **WHEN** a configured `MoneyAmount` carries a known UUID but stale name or symbol metadata
|
||||||
|
- **THEN** the service processes the current canonical currency resolved for that UUID
|
||||||
|
|
||||||
|
#### Scenario: Priority is preserved
|
||||||
|
- **WHEN** multiple configured entries have rounded amounts at/above their own minimum and usable Solana data
|
||||||
|
- **THEN** the earliest entry in the defensively copied list is the only input selected in that iteration
|
||||||
|
|
||||||
|
#### Scenario: Empty input list
|
||||||
|
- **WHEN** no buyback inputs are configured but the wallet contains EVE
|
||||||
|
- **THEN** the service skips input selection and still evaluates the complete EVE balance for burning
|
||||||
|
|
||||||
|
#### Scenario: Maximum list order is independent
|
||||||
|
- **WHEN** maxima are supplied in a different order from the reserve list or carry stale display metadata for known UUIDs
|
||||||
|
- **THEN** each input receives the maximum for its UUID and reserve-list priority is preserved
|
||||||
|
|
||||||
|
#### Scenario: Caller changes its configuration lists
|
||||||
|
- **WHEN** a caller changes any supplied list after construction
|
||||||
|
- **THEN** the accepted currencies, their priority, reserves, minima and maxima remain as validated during construction
|
||||||
|
|
||||||
|
#### Scenario: Minimum list order and display metadata are independent
|
||||||
|
- **WHEN** minima are reordered or use stale names/symbols for known canonical UUIDs
|
||||||
|
- **THEN** each input receives its UUID's minimum and only reserve-list order controls priority
|
||||||
|
|
||||||
|
### Requirement: Complete constructor validation
|
||||||
|
Construction SHALL require all injected services, all three input configuration lists, and durations to be non-null; every list entry, entry amount, entry currency, and currency UUID to be non-null; each reserve to be zero or greater; each minimum and maximum to be strictly positive; each minimum to be no greater than its matched maximum; each UUID to be known to the injected Currency Identity Service; UUIDs to be unique within each list; both minimum and maximum UUID sets to exactly equal the accepted reserve UUID set; and EVE not to be an input currency. It SHALL require slippage to be from 0 through 10000 basis points inclusive, `maximumPriceAge` to be greater than zero milliseconds, and both finalization timeout and iteration interval to be positive and non-zero.
|
||||||
|
|
||||||
|
Every configured non-SOL UUID SHALL resolve through reverse CIS lookup to exactly one external reference in the `solana-mint` namespace, and that reference's external ID SHALL be non-null and non-blank. Construction SHALL fail for a missing, ambiguous, null, or blank non-SOL mint representation. These burner-specific rules SHALL NOT change the general-purpose `MoneyAmount` contract.
|
||||||
|
|
||||||
|
#### Scenario: Valid configuration
|
||||||
|
- **WHEN** every dependency and value is valid, reserves are non-negative, every accepted input has exactly one positive minimum and maximum with minimum <= maximum, UUIDs are distinct and known within each list, EVE is absent, and every non-SOL input has one non-blank Solana mint reference
|
||||||
|
- **THEN** construction succeeds and preserves the configured priority order
|
||||||
|
|
||||||
|
#### Scenario: Invalid reserve or operating limit
|
||||||
|
- **WHEN** a reserve is negative or null, a token maximum is null or non-positive, slippage is outside its inclusive range, maximum price age is non-positive, or either duration is null or non-positive
|
||||||
|
- **THEN** construction fails before a worker or transaction is created
|
||||||
|
|
||||||
|
#### Scenario: Invalid input identity
|
||||||
|
- **WHEN** an entry or its currency or UUID is null, a UUID is unknown or duplicated, or the EVE UUID is configured as input
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the invalid configuration
|
||||||
|
|
||||||
|
#### Scenario: Non-SOL representation is not unique and usable
|
||||||
|
- **WHEN** a configured non-SOL currency has zero or multiple `solana-mint` references or its sole external ID is null or blank
|
||||||
|
- **THEN** construction fails without adding mint or token-program selection to Evelyn's configuration model
|
||||||
|
|
||||||
|
#### Scenario: Maximum configuration is incomplete or ambiguous
|
||||||
|
- **WHEN** a maximum is missing for an accepted input, duplicated by UUID, or supplied for an unaccepted currency
|
||||||
|
- **THEN** construction fails before a worker or transaction is created
|
||||||
|
|
||||||
|
#### Scenario: Invalid maximum identity or value
|
||||||
|
- **WHEN** a maximum entry, amount, currency or UUID is null, or its UUID is unknown or resolves to an invalid canonical identity
|
||||||
|
- **THEN** construction fails with a diagnostic identifying the invalid maximum configuration
|
||||||
|
|
||||||
|
#### Scenario: Invalid minimum configuration
|
||||||
|
- **WHEN** the minimum list or an entry, amount, currency or UUID is null; a UUID is unknown, duplicated, extra or missing; a minimum is non-positive; or it exceeds that UUID's maximum
|
||||||
|
- **THEN** construction fails before a worker or transaction is created
|
||||||
|
|
||||||
|
#### Scenario: Minimum equals maximum
|
||||||
|
- **WHEN** a strictly positive minimum is numerically equal to its matched maximum
|
||||||
|
- **THEN** that amount configuration is accepted without requiring equal decimal scales
|
||||||
|
|
||||||
|
#### Scenario: Direct EVE only configuration
|
||||||
|
- **WHEN** all three configuration lists are empty
|
||||||
|
- **THEN** construction succeeds and direct EVE remains eligible for burning
|
||||||
|
|
||||||
|
### Requirement: Exact token-capped amount and per-currency reserve
|
||||||
|
The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and actual rounded amounts below that UUID's minimum, continuing to later inputs in the same iteration without counting a swap attempt. Equality SHALL qualify. It SHALL never round upward, top up from reserves or exceed either existing bound. Input selection SHALL require no input/USDC Ticker observation. Minima and maxima SHALL constrain buyback inputs only; EVE burns SHALL remain uncapped and unaffected. Native SOL reserves SHALL use observed balance without estimating future fees.
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC maximum is lower than spendable balance
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
|
||||||
|
- **THEN** 0.2 SyrupUSDC is selected without requesting a SyrupUSDC/USDC observation
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC spendable balance is lower than the maximum
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.245, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
|
||||||
|
- **THEN** 0.150 SyrupUSDC is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Reserve consumes the balance
|
||||||
|
- **WHEN** the current balance is equal to or below that entry's minimum reserve
|
||||||
|
- **THEN** no amount of that currency is intentionally swapped
|
||||||
|
|
||||||
|
#### Scenario: USDT maximum preserves the production bound
|
||||||
|
- **WHEN** USDT balance is 0.35, its reserve is 0.1, its minimum is 0.05 and its maximum is 0.25
|
||||||
|
- **THEN** 0.25 USDT is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Precision rounding never increases the spend
|
||||||
|
- **WHEN** the lesser of spendable balance and maximum is 0.1509999, the mint supports six decimals and its minimum is no greater than 0.150999
|
||||||
|
- **THEN** the service selects 0.150999, and if downward rounding instead produces zero the input is skipped
|
||||||
|
|
||||||
|
#### Scenario: Native transaction fees
|
||||||
|
- **WHEN** native SOL is selected with a configured reserve
|
||||||
|
- **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
|
||||||
|
|
||||||
|
#### Scenario: USDT below and at its minimum
|
||||||
|
- **WHEN** USDT reserve is 0.1 and minimum is 0.05
|
||||||
|
- **THEN** balances 0.14 and 0.149999 are skipped, while balance 0.15 selects exactly 0.05
|
||||||
|
|
||||||
|
#### Scenario: Tiny first input permits useful later input
|
||||||
|
- **WHEN** USDT balance is 0.100001 with reserve 0.1/minimum 0.05, and SyrupUSDC balance is 0.502048 with reserve 0.095/minimum 0.045/maximum 0.2
|
||||||
|
- **THEN** the USDT remainder is skipped and 0.2 SyrupUSDC is the only attempted swap
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC minimum boundary
|
||||||
|
- **WHEN** SyrupUSDC reserve is 0.095 and minimum is 0.045
|
||||||
|
- **THEN** balance 0.14 selects exactly 0.045 while 0.139999 is skipped
|
||||||
|
|
||||||
|
#### Scenario: Actual rounded candidate falls below a fractional minimum
|
||||||
|
- **WHEN** six-decimal input precision rounds candidate 0.0500009 down to 0.050000 and its minimum is 0.0500001
|
||||||
|
- **THEN** the candidate is skipped without rounding up or using reserves
|
||||||
|
|
||||||
|
#### Scenario: Input minima do not suppress direct EVE burn
|
||||||
|
- **WHEN** every configured input is below its minimum but the wallet contains EVE with a usable EVE/USDT price
|
||||||
|
- **THEN** the complete EVE balance is still evaluated and burned by the existing rules
|
||||||
|
|
||||||
|
### Requirement: Production burner accepts USDT and SyrupUSDC
|
||||||
|
The production Hub SHALL configure USDT first with reserve 0.1, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 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 exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority and retains the other existing production arguments
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Configure and qualify minimum buybacks
|
||||||
|
|
||||||
|
- [x] 1.1 Add immutable canonical-UUID minima and constructor validation; verify reordered/copied configuration, invalid values and minimum <= maximum with a temporary offline harness.
|
||||||
|
- [x] 1.2 Apply minima to actual rounded candidates and update domain/constructor JavaDoc; verify issue examples, precision/scale boundaries, later-input continuation and unchanged reserve/max bounds offline.
|
||||||
|
- [x] 1.3 Add exact production minima and update Evelyn configuration documentation; review USDT priority and all other unchanged production arguments.
|
||||||
|
|
||||||
|
## 2. Verify and finalize integration
|
||||||
|
|
||||||
|
- [x] 2.1 Compile existing main/test sources and run focused temporary selection/config checks plus the adapted #91 recovery harness; record results and review unchanged execution, Ticker, burn and recovery code.
|
||||||
|
- [x] 2.2 Strictly validate the change, sync and validate the main spec, check whitespace and archive the completed change after confirming all requirements/tasks are satisfied.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified on 2026-10-06 with JDK `/usr/local/software/java/jfx-25`.
|
||||||
|
|
||||||
|
## Compilation and controlled checks
|
||||||
|
|
||||||
|
- Parent compilation: `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 6s**; `detagMain`, `compileJava` and `compileTestJava` all executed against the final source.
|
||||||
|
- Temporary harnesses compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue92 /tmp/evelyn-issue92/Issue92Verification.java /tmp/evelyn-issue92/Issue91Verification.java`.
|
||||||
|
- `java --enable-preview -cp '/tmp/evelyn-issue92:build/classes/java/main:build/libs/*' Issue92Verification` → **PASS: 89 focused minimum-amount assertions**.
|
||||||
|
- The same invocation for `Issue91Verification` → **PASS: 102 recovery assertions**. Its original temporary file is preserved in `/tmp/evelyn-issue91`; the copy changes only the burner constructor call to add SyrupUSDC minimum 0.045.
|
||||||
|
- All wallet, swap, HTTP and pricing dependencies are controlled. No live financial operation or real RPC request occurs.
|
||||||
|
|
||||||
|
The #92 harness covers every issue example, equality across decimal scales, minimum == maximum, fractional minima above rounded candidates, downward rounding of maximum, zero after precision rounding, next-input continuation, canonical UUID matching with reordered/stale display metadata, caller mutations of all three copied lists, invalid minima, preserved maximum validation, direct EVE when inputs are below minima or all three lists are empty, stale EVE price and at most one attempted qualifying swap even after failure.
|
||||||
|
|
||||||
|
An initial temporary null-UUID fixture attempted to construct an invalid CurrencyType, whose existing constructor already rejects null IDs before burner configuration. The check was moved to that proper pre-burner rejection point, and the #92 harness rerun passed. No production fix was required.
|
||||||
|
|
||||||
|
## Review and scope
|
||||||
|
|
||||||
|
Parent independent source/documentation review found no material findings. Static diff review confirms the changes are confined to the reference constructor/amount configuration validator, rounded-candidate qualification, domain JavaDoc, production minima and Evelyn configuration docs. Reserves/maxima, USDT priority, pending recovery, Jupiter, Ticker, token discovery, burn/notification and lifecycle are preserved. No ValueTag, deployment file or permanent automated test changed.
|
||||||
|
|
||||||
|
Strict validation of the active change and synced main specification passed. All four delta requirements match the main spec exactly; unrelated requirements/scenarios remain intact. Existing long-requirement informational hints remain under the rule that modified requirement blocks stay whole. `git diff --check` passed. All five tasks and four planning artifacts are complete.
|
||||||
|
|
||||||
|
After archival, `openspec list --json` reports no active changes. `openspec validate --archived --json --report findings` passed for all 30 archived changes, with 0 failures and 0 findings.
|
||||||
|
|
||||||
|
No live integration, deployment, push or Gitea write was performed. Temporary harnesses remain outside the repository; no permanent automated tests were added.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-06
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for the operational need. The burner already reloads EVE after its independent buyback stage, retains known burn signatures before finalization, and prioritizes pending burns. It can reuse supported-program and mint-supply precision validation already used for input selection. Monetary values and CurrencyType are immutable.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Preserve a configured EVE reserve and accumulate small buys/deposits until a representable burn amount qualifies, retaining existing transaction and notification safety.
|
||||||
|
|
||||||
|
**Non-Goals:** Fixed burn batches, maximum burn size, a new configuration framework, notification batching, altered buybacks, persistent reconciliation or live deployment.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Add `minimumBurnAmount` and `eveReserve` MoneyAmount constructor arguments beside input bounds. Validate non-null amount/currency/UUID, canonical EVE UUID, positive minimum and non-negative reserve before startup, copying into canonical EVE MoneyAmount values. Stale display metadata is accepted by UUID. A separate record or ValueTag adds no needed domain safety.
|
||||||
|
2. Reload EVE after buybacks. Subtract the reserve and skip non-positive available balance, then resolve EVE mint precision using its detected token program. Round DOWN and compare that actual amount to the exact unrounded minimum. Skip non-positive/below-minimum amounts before price lookup/submission. Rounding configuration up or down would alter the owner's reserve/minimum; instead any fractional remainder stays in the wallet.
|
||||||
|
3. Submit all eligible EVE above reserve, using the same amount for price multiplication and PendingBurn. Keep pending-burn priority and awaiting unchanged so later thresholds/balances never rebuild an unresolved burn.
|
||||||
|
4. The owner chose production minimum **0.1 EVE** and reserve **0.01 EVE** on 2026-10-06. Supply both using canonical EVE in the production Hub. Issue #93's 1/0.1 values remain independent constructor acceptance examples, not production defaults.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [EVE mint metadata unavailable] → Postpone burning without notification, using existing warning and interruption behavior; buybacks have already been evaluated independently.
|
||||||
|
- [Configuration precision exceeds mint precision] → Round only the eligible burn amount DOWN; compare to exact minimum and retain at least the exact reserve.
|
||||||
|
- [Unknown transaction outcome] → Preserve original signature and existing process-local suspension/reconciliation behavior. Restart still requires external reconciliation.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Compile the reference sources and update the production construction call together. Verify controlled threshold, precision, accumulation, direct-deposit, validation, pending-state and notification cases offline, synchronize specs and archive. Deployment is a separate task; no private deployment files or live transactions are touched. Rollback requires restoring the prior constructor and Hub call together, after reconciling uncertain transactions.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Small Evelyn buybacks currently create tiny burns and excessive proof notifications. Accumulating EVE for larger burns while retaining a wallet reserve supports predictable Evelyn operation without changing its small buybacks.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add independently configured EVE `MoneyAmount` values for a strictly positive burn minimum and non-negative retained reserve.
|
||||||
|
- Burn the entire balance above reserve, rounded down to mint precision, only when that actual amount reaches the minimum.
|
||||||
|
- Keep buybacks independent and preserve pending transaction reconciliation and successful FINALIZED proof delivery using the actual burned amount.
|
||||||
|
- Configure production with the owner's chosen minimum 0.1 EVE and reserve 0.01 EVE; retain issue #93's 1/0.1 examples as acceptance cases.
|
||||||
|
- **BREAKING**: Reference implementation constructor requires the two burn policy amounts.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: EVE burn policy configuration/validation, precision-aware eligibility above reserve, submitted/notification amounts and production settings.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Reference burner implementation and its domain JavaDoc, production Hub constructor, Evelyn configuration documentation and directly affected OpenSpec requirements. No new ValueTags, administration API, buyback policy, scheduling, deployment changes or permanent tests. Verification uses controlled offline dependencies without live transactions.
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Independent EVE burn policy configuration
|
||||||
|
The constructor SHALL accept minimumBurnAmount and eveReserve as EVE-denominated MoneyAmount values identified by canonical EVE UUID regardless of supplied display metadata. Both values, their amounts, currencies and UUIDs SHALL be non-null; minimum SHALL be strictly positive and reserve non-negative. Zero reserve SHALL be valid. Invalid burn configuration SHALL fail before worker startup without changing existing input validation.
|
||||||
|
|
||||||
|
#### Scenario: Invalid burn configuration
|
||||||
|
- **WHEN** either value, amount, currency or UUID is null, a UUID is not EVE, the minimum is zero/negative, or the reserve is negative
|
||||||
|
- **THEN** construction fails before creating a worker or transaction
|
||||||
|
|
||||||
|
#### Scenario: Stale EVE metadata and zero reserve
|
||||||
|
- **WHEN** both values carry the EVE UUID with stale names/symbols and the reserve is zero
|
||||||
|
- **THEN** construction succeeds and uses canonical EVE identity with the configured exact numeric amounts
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Complete visible EVE balance is eligible
|
||||||
|
After the optional swap attempt, the service SHALL detect the fixed EVE mint's supported token program internally and reload the wallet's complete available EVE balance. A missing EVE mint account, unsupported program, absent wallet token account, zero balance, or negative/invalid balance SHALL cause no burn or notification in that iteration. The service SHALL subtract its configured EVE reserve, clamp negative available amounts to zero and round down to validated EVE mint precision using the detected token program. It SHALL submit a new burn only when the actual rounded amount is positive and at least the configured minimum burn amount; equality qualifies. It SHALL burn the entire eligible amount above reserve, without a maximum or fixed batch size, including EVE deposited before the iteration or visible after a failed or absent swap. Below threshold or with unusable mint precision it SHALL retain the EVE and send no burn notification. This threshold SHALL NOT suppress independent buybacks.
|
||||||
|
|
||||||
|
#### Scenario: Existing EVE without buyback
|
||||||
|
- **WHEN** no swap is performed but the wallet contains a positive EVE balance
|
||||||
|
- **THEN** the service evaluates that complete balance under the EVE reserve, precision and burn-minimum rules
|
||||||
|
|
||||||
|
#### Scenario: EVE account is absent or empty
|
||||||
|
- **WHEN** the wallet has no EVE token account or its complete balance is zero
|
||||||
|
- **THEN** the iteration submits no burn and sends no notification
|
||||||
|
|
||||||
|
#### Scenario: Issue example threshold and reserve
|
||||||
|
- **WHEN** minimum is 1 EVE and reserve is 0.1 EVE
|
||||||
|
- **THEN** balances 0, 0.05, 0.1, 1.05 and 1.099999 produce no burn; balance 1.1 burns 1 and balance 1.35 burns 1.25, retaining 0.1 after success
|
||||||
|
|
||||||
|
#### Scenario: Accumulation across small buybacks
|
||||||
|
- **WHEN** individually eligible small buybacks accumulate EVE over multiple iterations
|
||||||
|
- **THEN** each below-threshold iteration still permits its buyback and sends no burn message; the first qualifying iteration burns all eligible EVE and subsequent accumulation starts with the retained reserve
|
||||||
|
|
||||||
|
#### Scenario: Exact configuration exceeds mint precision
|
||||||
|
- **WHEN** six-decimal mint precision rounds available 1.0000009 to 1.000000 while minimum is 1.0000001
|
||||||
|
- **THEN** no burn occurs; no amount is rounded upward and the reserve is never consumed to reach the minimum
|
||||||
|
|
||||||
|
### Requirement: Fresh EVE value is required before submission
|
||||||
|
Immediately before a new burn, the service SHALL obtain the latest canonical EVE/USDT observation and require a positive price, non-null observation timestamp, and age no greater than `maximumPriceAge` milliseconds. USDT SHALL be treated as USD for notification value, calculated as the actual rounded eligible EVE amount above reserve multiplied by that fresh price. If the pair is unsupported or the observation is missing, invalid, or stale, the service SHALL warn, postpone the burn, send no notification, and retry only by re-evaluating state in a later scheduled iteration.
|
||||||
|
|
||||||
|
#### Scenario: Fresh EVE price exists
|
||||||
|
- **WHEN** a positive rounded EVE amount at or above the burn minimum and sufficiently fresh positive EVE/USDT observation are available
|
||||||
|
- **THEN** the service calculates the notification's USD value from that actual eligible burn amount, excluding reserve before submitting the burn
|
||||||
|
|
||||||
|
#### Scenario: EVE price is stale
|
||||||
|
- **WHEN** EVE is present but its observation is older than the configured age limit
|
||||||
|
- **THEN** the service leaves EVE unburned and sends no notification in that iteration
|
||||||
|
|
||||||
|
### Requirement: Single complete-balance burn submission and pending state
|
||||||
|
For a positive rounded EVE amount at or above the minimum with a usable price, the service SHALL call the wallet burn operation exactly once with the fixed EVE mint and entire eligible rounded amount above reserve. It SHALL NOT automatically retry, rebuild, re-sign, or resubmit within or across iterations. Immediately after a non-blank signature is returned, it SHALL retain process-local pending data containing that signature, actual submitted EVE amount excluding reserve, and its calculated notification value or complete notification text before awaiting the signature.
|
||||||
|
|
||||||
|
If burn submission fails without a usable signature and its outcome may be unknown, including an I/O or interruption path whose contract permits post-submission uncertainty, the service SHALL conservatively suspend further burn submissions for the current process. It MAY continue ordinary iteration work that does not submit another burn. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation.
|
||||||
|
|
||||||
|
#### Scenario: Burn returns a signature
|
||||||
|
- **WHEN** the wallet submits the entire eligible rounded amount above reserve and returns a non-blank signature
|
||||||
|
- **THEN** pending state is retained before the service begins finalization waiting
|
||||||
|
|
||||||
|
#### Scenario: Submission outcome may be unknown without a signature
|
||||||
|
- **WHEN** burn submission fails or is interrupted without providing a usable signature and may already have reached Solana
|
||||||
|
- **THEN** the service performs no further burn submission until external reconciliation and a complete process restart
|
||||||
|
|
||||||
|
#### Scenario: Pending burn bypasses new threshold evaluation
|
||||||
|
- **WHEN** a retained burn signature remains pending while balance is now below the minimum or at the reserve
|
||||||
|
- **THEN** only the original signature is awaited, its original submitted amount/value are preserved and it is never re-evaluated, rebuilt or resubmitted
|
||||||
|
|
||||||
|
### Requirement: Exact token-capped amount and per-currency reserve
|
||||||
|
The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and actual rounded amounts below that UUID's minimum, continuing to later inputs in the same iteration without counting a swap attempt. Equality SHALL qualify. It SHALL never round upward, top up from reserves or exceed either existing bound. Input selection SHALL require no input/USDC Ticker observation. Minima and maxima SHALL constrain buyback inputs only; EVE burns SHALL remain uncapped and governed independently by their own EVE minimum and reserve. Native SOL reserves SHALL use observed balance without estimating future fees.
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC maximum is lower than spendable balance
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
|
||||||
|
- **THEN** 0.2 SyrupUSDC is selected without requesting a SyrupUSDC/USDC observation
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC spendable balance is lower than the maximum
|
||||||
|
- **WHEN** SyrupUSDC balance is 0.245, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
|
||||||
|
- **THEN** 0.150 SyrupUSDC is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Reserve consumes the balance
|
||||||
|
- **WHEN** the current balance is equal to or below that entry's minimum reserve
|
||||||
|
- **THEN** no amount of that currency is intentionally swapped
|
||||||
|
|
||||||
|
#### Scenario: USDT maximum preserves the production bound
|
||||||
|
- **WHEN** USDT balance is 0.35, its reserve is 0.1, its minimum is 0.05 and its maximum is 0.25
|
||||||
|
- **THEN** 0.25 USDT is selected before downward mint-precision rounding
|
||||||
|
|
||||||
|
#### Scenario: Precision rounding never increases the spend
|
||||||
|
- **WHEN** the lesser of spendable balance and maximum is 0.1509999, the mint supports six decimals and its minimum is no greater than 0.150999
|
||||||
|
- **THEN** the service selects 0.150999, and if downward rounding instead produces zero the input is skipped
|
||||||
|
|
||||||
|
#### Scenario: Native transaction fees
|
||||||
|
- **WHEN** native SOL is selected with a configured reserve
|
||||||
|
- **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
|
||||||
|
|
||||||
|
#### Scenario: USDT below and at its minimum
|
||||||
|
- **WHEN** USDT reserve is 0.1 and minimum is 0.05
|
||||||
|
- **THEN** balances 0.14 and 0.149999 are skipped, while balance 0.15 selects exactly 0.05
|
||||||
|
|
||||||
|
#### Scenario: Tiny first input permits useful later input
|
||||||
|
- **WHEN** USDT balance is 0.100001 with reserve 0.1/minimum 0.05, and SyrupUSDC balance is 0.502048 with reserve 0.095/minimum 0.045/maximum 0.2
|
||||||
|
- **THEN** the USDT remainder is skipped and 0.2 SyrupUSDC is the only attempted swap
|
||||||
|
|
||||||
|
#### Scenario: SyrupUSDC minimum boundary
|
||||||
|
- **WHEN** SyrupUSDC reserve is 0.095 and minimum is 0.045
|
||||||
|
- **THEN** balance 0.14 selects exactly 0.045 while 0.139999 is skipped
|
||||||
|
|
||||||
|
#### Scenario: Actual rounded candidate falls below a fractional minimum
|
||||||
|
- **WHEN** six-decimal input precision rounds candidate 0.0500009 down to 0.050000 and its minimum is 0.0500001
|
||||||
|
- **THEN** the candidate is skipped without rounding up or using reserves
|
||||||
|
|
||||||
|
#### Scenario: Input minima do not suppress direct EVE burn
|
||||||
|
- **WHEN** every configured input is below its minimum but the wallet contains EVE with a usable EVE/USDT price
|
||||||
|
- **THEN** the complete EVE balance is still evaluated and its rounded eligible amount above the EVE reserve is burned if it meets the EVE burn minimum
|
||||||
|
|
||||||
|
### Requirement: Production burner accepts USDT and SyrupUSDC
|
||||||
|
The production Hub SHALL configure USDT first with reserve 0.1, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The production EVE burn minimum SHALL be 0.1 EVE and its retained reserve SHALL be 0.01 EVE, as selected by the owner on 2026-10-06, configured as MoneyAmount values using the canonical EVE UUID. 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 exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority, supplies minimum burn 0.1 EVE/reserve 0.01 EVE and retains the other existing production arguments
|
||||||
|
|
||||||
|
#### Scenario: Production burn threshold
|
||||||
|
- **WHEN** production EVE balance is below 0.11, exactly 0.11, or 0.135 with usable mint precision and fresh price
|
||||||
|
- **THEN** below 0.11 produces no burn, 0.11 burns 0.1 and 0.135 burns 0.125; successful burns retain 0.01 EVE
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. EVE burn policy
|
||||||
|
|
||||||
|
- [x] 1.1 Add canonical EVE MoneyAmount minimum/reserve constructor validation and constructor JavaDoc; verify invalid/null/wrong UUID values fail before worker/transaction activity, stale metadata works and zero reserve is valid in controlled offline checks.
|
||||||
|
- [x] 1.2 Replace full-balance submission with reserve-aware downward precision rounding and actual-minimum qualification; update domain JavaDoc and verify issue boundaries, rounding, direct deposits, accumulation, independent buybacks and actual pending/notification amounts with controlled dependencies.
|
||||||
|
|
||||||
|
## 2. Production configuration
|
||||||
|
|
||||||
|
- [x] 2.1 Supply owner's selected minimum 0.1 EVE and reserve 0.01 EVE in the production Hub, update Evelyn docs and verify source arguments plus production 0.11 threshold/0.135 balance examples offline.
|
||||||
|
|
||||||
|
## 3. Integration and completion
|
||||||
|
|
||||||
|
- [x] 3.1 Compile relevant sources, verify pending burn/swap reconciliation and suspension regressions without live transactions, and record results in verification.md; run strict OpenSpec validation and git diff --check.
|
||||||
|
- [x] 3.2 Synchronize directly affected main specification, verify all delta blocks match while unrelated content survives, run strict validation and archive completed artifacts/tasks.
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified on 2026-10-06 with `/usr/local/software/java/jfx-25`.
|
||||||
|
|
||||||
|
## Compilation and controlled acceptance checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 6s**; normal Detag and both source compilations executed. The initial sandbox attempt could not create Gradle's existing cache lock outside the workspace; the same compilation passed with approved cache access.
|
||||||
|
- Temporary harness sources in `/tmp/evelyn-issue93` compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue93`.
|
||||||
|
- `java --enable-preview -cp '/tmp/evelyn-issue93:build/classes/java/main:build/libs/*' Issue93Verification` → **PASS: 235 focused burn-policy assertions**.
|
||||||
|
- Same invocation for the copied `Issue92Verification` → **PASS: 89 buyback-minimum regression assertions**.
|
||||||
|
- Same invocation for the copied `Issue91Verification`, restricted to its burner checks → **PASS: 63 pending-swap/recovery regression assertions**. Unchanged RPC/Jupiter library checks were not rerun.
|
||||||
|
- Production source check confirms canonical EVE minimum **0.1** and reserve **0.01** in the correct constructor order and preserved input bounds, hourly interval, freshness and finalization arguments.
|
||||||
|
|
||||||
|
All wallet, transaction, chain, pricing and notification dependencies are controlled. No real RPC, signing or live financial operation occurs. No permanent automated tests were added. The older temporary harness copies add only a positive EVE burn minimum of 0.000001 and zero reserve to their constructor calls, retaining their original buyback/recovery expectations; original temporary files are preserved.
|
||||||
|
|
||||||
|
The #93 checks cover every issue #93 1/0.1 example and production 0.1/0.01 boundaries, numerical equality across decimal scales, zero reserve, fractional minimum above the rounded candidate, fractional reserve preservation, zero after rounding, both SPL Token and Token-2022 precision, missing/unsupported mint data, supply/mint/program mismatch, invalid decimals, I/O and interruption, missing/negative balance, stale price, repeated small buyback accumulation and subsequent reserve-based accumulation, direct deposits with absent/below-minimum inputs, no burn maximum, failed/unknown buybacks, original pending signature and amount/value across timeout/I/O/interruption, failed finalization without notification, once-only notification after delayed finalization, delivery failure and signature-less burn suspension with continued buybacks. Null/invalid numeric configuration, wrong/unknown UUIDs and stale EVE metadata are checked before worker/transaction activity; CurrencyType itself rejects null UUIDs before burner configuration.
|
||||||
|
|
||||||
|
An initial temporary harness compile failed because its notification-error fixture used Throwable rather than the transport's IOException contract; the fixture was corrected. Its default successful swap output was also corrected from zero to a positive amount, preventing fixture-only result validation failures. The final checks above pass against the unchanged production implementation.
|
||||||
|
|
||||||
|
## Review and workflow
|
||||||
|
|
||||||
|
Parent independent source/design/documentation and harness review found no material findings. Two delta scenario wording fixes were applied to state the actual eligible amount and grammatically separate the new production burn settings. Scope is limited to constructor burn policy, EVE eligibility/actual amount use, JavaDoc, production arguments, Evelyn docs and their specification. Buyback limits, priority, pending recovery, lifecycle, Jupiter, Ticker, ValueTags and private deployment files are preserved.
|
||||||
|
|
||||||
|
Strict active-change validation and `git diff --check` passed before synchronization. Final strict validation of the active change and synchronized main specification passed. Six delta requirement blocks match the main specification exactly; 12 untouched requirements remain byte-for-byte intact. The purpose now describes accumulated, threshold-qualified burns above reserve. Existing long-requirement informational hints remain because modified requirement blocks stay whole.
|
||||||
|
|
||||||
|
An initial temporary synchronization matcher allowed a requirement title to span lines and copied an invalid delta header into the main specification. Strict validation caught this before archive. The matcher was corrected and the main-spec edit rebuilt from the clean starting version plus the six intended blocks, then compared for exact delta matching and untouched-content preservation. Final strict validation passes.
|
||||||
|
|
||||||
|
All four planning artifacts and all five tasks are complete. The completed change is archived at `openspec/changes/archive/2026-10-06-93-add-minimum-eve-burn-and-reserve`. After archival, `openspec list --json` reports no active changes, and `openspec validate --archived --json --report findings` passed for all **31** archived changes with **0 failures**. Final whitespace validation also passes.
|
||||||
|
|
||||||
|
No deployment, push, Gitea write or live integration was performed.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-06
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation and the two delta specifications for required behavior. The burner already selects one rounded, reserve/minimum/maximum-qualified input, invokes Jupiter at most once, and then independently reloads EVE for burn evaluation. Its fresh canonical EVE/USDT reader is also used for burn valuation. The Hub constructs processes in the manually configured order and rejects unavailable process dependencies.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Insert the purchase condition at the last point before a new Jupiter attempt, use the corresponding live reference API, and retain independent burn and pending-transaction behavior.
|
||||||
|
|
||||||
|
**Non-Goals:** A general price-policy framework, another clock/configuration/freshness option, duplicated curve calculations, stored-history decisions, or changes to transaction execution, Ticker sources, lifecycle and deployment selection.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Add a required `EvelynStatusService` constructor dependency beside Ticker. Production composition uses the existing `requireProcessDependency` guard for `evelyn.status.prod`. The Hub does not infer or start dependencies; operators must place Ticker and Production status before the burner. Constructing a private reference calculator in the burner would duplicate ownership and risk policy drift.
|
||||||
|
2. After the first eligible rounded input is selected, reuse `findFreshPrice(eveUsdtTradingPair)`, obtain the reference through `getReferencePrice(Instant.now())`, validate its canonical pair and positive amount, and compare with `BigDecimal.compareTo`. This avoids division/index rounding and keeps the check close to submission. Checking before input selection would age the decision during balance/mint work; history/chart reads would use stale sample time.
|
||||||
|
3. A rejected condition returns only from `attemptAtMostOneSwap`, preserving the unconditional later `processCompleteEveBalance`. No other input can bypass a globally rejected EVE price. Missing/invalid reference or runtime lookup failure produces a safe warning; an at/above comparison logs the values and decision instant. No suspension state is set. Burn valuation independently reads its existing fresh price and never depends on reference availability.
|
||||||
|
4. Pending burn and swap paths stay ahead of input selection. Reconciliation is read-only regardless of price, and only a newly eligible purchase after reconciliation invokes the gate. Amount limits, precision, unknown-outcome handling and notifications remain as existing contracts define.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Market movement after observation] → Use the existing maximum price age and the last purchase-selection point; Jupiter execution and slippage rules remain unchanged.
|
||||||
|
- [Reference service failure] → Skip the purchase for this iteration, keep burn processing and re-evaluate next time; log exception type without arbitrary provider text.
|
||||||
|
- [Missing deployment predecessor] → Fail clearly through existing Hub dependency validation; document order without editing private deployment configuration.
|
||||||
|
- [Unknown transaction after restart] → Retain current pending-state/restart contracts; the price gate cannot replace reconciliation.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Compile and verify offline, then review the change. A later separately authorized deployment must have `assetaz.ticker.default` and `evelyn.status.prod` before a selected `evelyn.iou-burner.prod`. This implementation request changes no live configuration, performs no transactions and deploys nothing. Rollback restores the preceding application version and its constructor composition; uncertain transactions must first be reconciled under the existing restart rules.
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Issue #94 requires Evelyn IOU buybacks to reduce supply specifically while the Evelyn Price Index is below zero. The burner currently buys whenever an input is eligible, so it needs the existing reference-price policy before submitting a new purchase while preserving independent EVE burning.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Permit new buybacks only with a fresh canonical EVE/USDT price strictly below the corresponding Evelyn status service's reference price at the current decision instant.
|
||||||
|
- Skip only the purchase when either price is unusable or the market price is at/above reference; continue existing EVE burn processing and re-evaluate next iteration.
|
||||||
|
- Preserve pending-burn priority, read-only pending-swap reconciliation, at most one new swap, and all existing amount, reserve, precision and transaction rules.
|
||||||
|
- Inject the Production Evelyn status service through the existing Hub composition, documenting its explicit startup dependency and the purchase-only policy.
|
||||||
|
- Update constructor/domain JavaDoc and operating documentation and verify the boundaries and recovery paths offline.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: Gate new purchases on the existing reference-price API while keeping reconciliation and burns independent.
|
||||||
|
- `nenjim-hub-configuration`: Supply the corresponding Production status service to the burner using existing explicit dependency ordering.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The burner constructor gains an `EvelynStatusService` dependency, supplied by `NenjimHubImpl`. Runtime input configuration, schedule, Ticker sources, history, ValueTags and live deployment configuration remain outside this change. Verification uses controlled offline collaborators without financial transactions or permanent test additions.
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: At most one confirmed swap attempt per iteration
|
||||||
|
After selecting a positive exact amount and satisfying the current below-reference price condition, 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, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The production EVE burn minimum SHALL be 0.1 EVE and its retained reserve SHALL be 0.01 EVE, as selected by the owner on 2026-10-06, configured as MoneyAmount values using the canonical EVE UUID. The production Hub SHALL supply its corresponding Production Evelyn status service for current reference-price decisions. 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 exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority, supplies minimum burn 0.1 EVE/reserve 0.01 EVE and the Production status service, and retains the other existing production arguments
|
||||||
|
|
||||||
|
#### Scenario: Production burn threshold
|
||||||
|
- **WHEN** production EVE balance is below 0.11, exactly 0.11, or 0.135 with usable mint precision and fresh price
|
||||||
|
- **THEN** below 0.11 produces no burn, 0.11 burns 0.1 and 0.135 burns 0.125; successful burns retain 0.01 EVE
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: New buybacks require a below-reference market price
|
||||||
|
An otherwise eligible new buyback SHALL require a fresh canonical EVE/USDT observation within the existing maximum price age and a usable canonical EVE/USDT reference price from the injected corresponding Evelyn status service at the current decision instant. Both prices SHALL be positive. Only actual price strictly below reference SHALL permit submission; equality SHALL not qualify. The existing public reference operation SHALL be reused without copying curve calculations or reading stored history.
|
||||||
|
|
||||||
|
#### Scenario: Strict boundary below reference
|
||||||
|
- **WHEN** an input is otherwise eligible and reference price is 15 USDT/EVE
|
||||||
|
- **THEN** market price 14.99 permits the existing buyback, while 15.00 and 15.01 submit no new swap from any input in that iteration
|
||||||
|
|
||||||
|
#### Scenario: Required prices are unusable
|
||||||
|
- **WHEN** market observation is missing, stale, incomplete, unavailable or has an invalid pair/non-positive price, or reference lookup throws, is missing or has an invalid pair/non-positive price
|
||||||
|
- **THEN** no new swap is submitted and a safe diagnostic explains why the purchase was skipped
|
||||||
|
|
||||||
|
#### Scenario: Reference uses the current decision instant
|
||||||
|
- **WHEN** the first usable input is ready for a new swap
|
||||||
|
- **THEN** the corresponding status service is asked for its reference price at the current instant rather than using a sampled history point or the observation timestamp
|
||||||
|
|
||||||
|
### Requirement: Price skips preserve independent burn processing and recovery
|
||||||
|
A rejected buyback price condition SHALL skip purchases only, continue complete EVE-balance processing under the existing independent burn requirements, and SHALL NOT create a suspension. Each subsequent otherwise eligible iteration SHALL re-evaluate prices. Pending-burn priority and read-only pending-swap reconciliation SHALL remain independent of the new-purchase condition; any newly permitted purchase after reconciliation SHALL pass the current price check.
|
||||||
|
|
||||||
|
#### Scenario: Existing EVE burns at or above reference
|
||||||
|
- **WHEN** market price is equal to or above reference and existing EVE satisfies its own reserve, minimum, precision, fresh valuation and transaction requirements
|
||||||
|
- **THEN** no new swap occurs and the existing eligible EVE is burned normally
|
||||||
|
|
||||||
|
#### Scenario: Reference unavailable but burn valuation is usable
|
||||||
|
- **WHEN** reference lookup is unavailable or invalid but existing EVE and its fresh canonical valuation satisfy the burn requirements
|
||||||
|
- **THEN** the purchase is skipped and the independent eligible burn still occurs
|
||||||
|
|
||||||
|
#### Scenario: Next scheduled iteration recovers
|
||||||
|
- **WHEN** one iteration skips a purchase because its prices do not qualify and a later otherwise eligible iteration has usable prices below reference
|
||||||
|
- **THEN** the later iteration can attempt its one new swap without a restart or manual reset
|
||||||
|
|
||||||
|
#### Scenario: Pending swap is resolved above reference
|
||||||
|
- **WHEN** a pending swap resolves read-only at or above reference
|
||||||
|
- **THEN** it is cleared normally, any otherwise eligible new purchase is price-checked and skipped, and existing EVE burn evaluation continues
|
||||||
|
|
||||||
|
#### Scenario: Unresolved swap and pending burn retain priority
|
||||||
|
- **WHEN** a swap remains unresolved or the iteration starts with a pending burn
|
||||||
|
- **THEN** existing transaction-priority rules apply without evaluating a new-purchase price condition or submitting a new swap
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is explicitly selected
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||||
|
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||||
|
|
||||||
|
#### Scenario: Production burner has its reference-price dependency
|
||||||
|
- **WHEN** `evelyn.iou-burner.prod` is reached after Ticker and `evelyn.status.prod`
|
||||||
|
- **THEN** the Hub injects the corresponding Production status service for buyback price decisions
|
||||||
|
|
||||||
|
#### Scenario: Production burner precedes its status dependency
|
||||||
|
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||||
|
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Purchase policy and independent burns
|
||||||
|
|
||||||
|
- [x] 1.1 Require the corresponding Evelyn status service and gate a fully eligible new purchase on fresh canonical actual price strictly below its current reference; verify constructor validation, below/equal/above and unavailable/invalid prices with controlled offline checks.
|
||||||
|
- [x] 1.2 Update constructor/domain JavaDoc and Evelyn operating documentation; verify price skips still burn eligible existing EVE, re-evaluate next iteration and preserve pending burn/swap priority in the same offline checks.
|
||||||
|
|
||||||
|
## 2. Hub composition
|
||||||
|
|
||||||
|
- [x] 2.1 Inject the Production status dependency through the existing Hub guard and document the explicit Ticker/status/burner order; verify production arguments and fail-fast dependency behavior without starting the Hub or editing deployment configuration.
|
||||||
|
|
||||||
|
## 3. Integrated verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile main and existing test sources through normal Gradle/Detag, execute focused offline purchase/burn/recovery checks and relevant prior burner harnesses, and record their final results without adding permanent tests or running live transactions.
|
||||||
|
- [x] 3.2 Validate the scoped OpenSpec change strictly and check whitespace and changed-file scope; deliver completed planning artifacts and verification evidence for review.
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified on 2026-10-06 with `/usr/local/software/java/jfx-25`.
|
||||||
|
|
||||||
|
## Compilation and controlled acceptance checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 8s**. Normal Gradle-integrated Detag and both source compilations executed. The initial sandbox attempt could not create the existing Gradle cache lock outside the workspace; the same compilation passed with approved cache access.
|
||||||
|
- Temporary sources in `/tmp/evelyn-issue94` compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue94`.
|
||||||
|
- `java --enable-preview -cp '/tmp/evelyn-issue94:build/classes/java/main:build/libs/*' Issue94Verification` → **PASS: 459 focused issue #94 assertions**.
|
||||||
|
- The copied `Issue93Verification` → **PASS: 246 burn-policy regression assertions**.
|
||||||
|
- The copied `Issue92Verification` → **PASS: 115 input-minimum regression assertions**.
|
||||||
|
- The copied `Issue91Verification`, restricted to burner checks → **PASS: 80 pending-swap/recovery regression assertions**. Unchanged Solana/Jupiter library checks were not rerun.
|
||||||
|
- Production composition source check verifies the corresponding Production status dependency and preserved input reserves/minima/maxima, EVE burn minimum/reserve, hourly interval, slippage, freshness and finalization arguments.
|
||||||
|
- An INFO/WARN-enabled controlled run verifies purchase-skip reasons for at/above reference, unavailable/invalid reference and unusable market price, without arbitrary reference/Ticker exception messages.
|
||||||
|
|
||||||
|
The **900** controlled assertions invoke the actual burner iteration with offline wallet, chain, swap, Ticker, reference and notification collaborators. No RPC, signing, live financial operation, application/Hub startup or deployment configuration read occurs. The Hub dependency guard is invoked directly without constructing a Hub. No permanent automated tests were added. Prior harness copies only add an always-below reference stub to constructor calls and update #92's expected canonical EVE/USDT lookup count for eligible purchases; original temporary files remain intact.
|
||||||
|
|
||||||
|
The #94 harness covers 14.99/15.00/15.01 against reference 15, numeric equality across scales, a close below-reference decimal, the current reference decision instant instead of observation/history time, complete independent burns and reserve preservation, missing/unavailable/non-positive/null-amount/wrong-pair market and reference data, stale market observations, fresh independent burn valuation after a purchase-price failure, stale burn valuation postponement, next-iteration recovery from every rejection category, input priority and amount limits, below-minimum input fallback, no-eligible-input/direct-EVE-only paths, manual swap suspension and null status validation. It verifies original-signature read-only reconciliation while price is above reference, unresolved retention, definitive success/failure/proven expiry followed by a newly rejected purchase, later below-reference recovery, and simultaneous pending burn/swap priority with no reference lookup or new transaction. PriceObservation itself rejects null observation timestamp/price before they can reach the burner.
|
||||||
|
|
||||||
|
An initial temporary harness compilation used the wrong fully qualified component-ID type; it was corrected to the repository's existing `com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId`. Final compiled checks pass. Concurrent initial Java invocations reported harmless performance-data lock warnings; the controlled diagnostic run disabled performance data. No production fix was needed for either verification-only issue.
|
||||||
|
|
||||||
|
## Review and workflow
|
||||||
|
|
||||||
|
Parent independent review of the proposal/specs/design, implementation, operating documentation and controlled harness found no actionable findings. The existing malformed burn JavaDoc line was corrected while updating that same list item. Scope is limited to the required reference dependency, new-purchase condition, Hub composition, related documentation and two OpenSpec capability deltas. Transaction execution, reconciliation, burn valuation/policy, ValueTags, scheduling and private deployment files retain their existing behavior.
|
||||||
|
|
||||||
|
Strict validation of the active change and `git diff --check` pass. All four planning artifact types and all five implementation tasks are complete. The active change is ready for review; main specifications have not been synchronized and the change has not been archived.
|
||||||
|
|
||||||
|
No deployment, commit, push, Gitea write or live integration was performed.
|
||||||
|
|
||||||
|
## Follow-up: clearer purchase-skip logs
|
||||||
|
|
||||||
|
The owner's requested logging refinement keeps price eligibility unchanged. INFO price skips now show a direct labelled comparison, for example `Skipping buyback as EVE price is at or above reference (market=15.01 >= reference=15 USDT/EVE)`, followed by the trading pair, decision instant and next action. Prices use plain decimals without redundant trailing zeros. WARN skips retain their reason; unavailable or invalid reference-price diagnostics also include the known market price.
|
||||||
|
|
||||||
|
`JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava --offline --no-daemon` passed. The existing temporary #94 harness passed its 459 assertions again with INFO/WARN enabled. Its captured output in `/tmp/evelyn-issue94/issue94-logging-refinement.log` confirms the numeric comparison for both equality and higher prices, reference failure details, missing/stale market diagnostics and exclusion of arbitrary exception messages. No new tests were added.
|
||||||
|
|
||||||
|
## Finalization
|
||||||
|
|
||||||
|
On 2026-10-06, the owner requested synchronization and archiving. Both capability deltas were merged into the main specifications for `evelyn-iou-burner-service` and `nenjim-hub-configuration`, preserving unrelated requirements and existing purpose sections. Every delta requirement was compared against the resulting main spec, and strict validation of both main specs and the completed change passed; only informational hints about long requirement descriptions were reported.
|
||||||
|
|
||||||
|
The seven completed change files, including `.openspec.yaml` and all five completed tasks, were archived intact to `openspec/changes/archive/2026-10-06-94-gate-buybacks-on-evelyn-reference-price`. OpenSpec reports no remaining active changes. This finalization records the subsequent sync/archive after the initial implementation and logging-refinement verification above.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-06
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation and the two delta specifications for required behavior. The burner already selects one rounded, reserve/minimum/maximum-qualified input, invokes Jupiter at most once, and then independently reloads EVE for burn evaluation. Its fresh canonical EVE/USDT reader is also used for burn valuation. The Hub constructs processes in the manually configured order and rejects unavailable process dependencies.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Insert the purchase condition at the last point before a new Jupiter attempt, use the corresponding live reference API, and retain independent burn and pending-transaction behavior.
|
||||||
|
|
||||||
|
**Non-Goals:** A general price-policy framework, another clock/configuration/freshness option, duplicated curve calculations, stored-history decisions, or changes to transaction execution, Ticker sources, lifecycle and deployment selection.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Add a required `EvelynStatusService` constructor dependency beside Ticker. Production composition uses the existing `requireProcessDependency` guard for `evelyn.status.prod`. The Hub does not infer or start dependencies; operators must place Ticker and Production status before the burner. Constructing a private reference calculator in the burner would duplicate ownership and risk policy drift.
|
||||||
|
2. After the first eligible rounded input is selected, reuse `findFreshPrice(eveUsdtTradingPair)`, obtain the reference through `getReferencePrice(Instant.now())`, validate its canonical pair and positive amount, and compare with `BigDecimal.compareTo`. This avoids division/index rounding and keeps the check close to submission. Checking before input selection would age the decision during balance/mint work; history/chart reads would use stale sample time.
|
||||||
|
3. A rejected condition returns only from `attemptAtMostOneSwap`, preserving the unconditional later `processCompleteEveBalance`. No other input can bypass a globally rejected EVE price. Missing/invalid reference or runtime lookup failure produces a safe warning; an at/above comparison logs the values and decision instant. No suspension state is set. Burn valuation independently reads its existing fresh price and never depends on reference availability.
|
||||||
|
4. Pending burn and swap paths stay ahead of input selection. Reconciliation is read-only regardless of price, and only a newly eligible purchase after reconciliation invokes the gate. Amount limits, precision, unknown-outcome handling and notifications remain as existing contracts define.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Market movement after observation] → Use the existing maximum price age and the last purchase-selection point; Jupiter execution and slippage rules remain unchanged.
|
||||||
|
- [Reference service failure] → Skip the purchase for this iteration, keep burn processing and re-evaluate next time; log exception type without arbitrary provider text.
|
||||||
|
- [Missing deployment predecessor] → Fail clearly through existing Hub dependency validation; document order without editing private deployment configuration.
|
||||||
|
- [Unknown transaction after restart] → Retain current pending-state/restart contracts; the price gate cannot replace reconciliation.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Compile and verify offline, then review the change. A later separately authorized deployment must have `assetaz.ticker.default` and `evelyn.status.prod` before a selected `evelyn.iou-burner.prod`. This implementation request changes no live configuration, performs no transactions and deploys nothing. Rollback restores the preceding application version and its constructor composition; uncertain transactions must first be reconciled under the existing restart rules.
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Issue #94 requires Evelyn IOU buybacks to reduce supply specifically while the Evelyn Price Index is below zero. The burner currently buys whenever an input is eligible, so it needs the existing reference-price policy before submitting a new purchase while preserving independent EVE burning.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Permit new buybacks only with a fresh canonical EVE/USDT price strictly below the corresponding Evelyn status service's reference price at the current decision instant.
|
||||||
|
- Skip only the purchase when either price is unusable or the market price is at/above reference; continue existing EVE burn processing and re-evaluate next iteration.
|
||||||
|
- Preserve pending-burn priority, read-only pending-swap reconciliation, at most one new swap, and all existing amount, reserve, precision and transaction rules.
|
||||||
|
- Inject the Production Evelyn status service through the existing Hub composition, documenting its explicit startup dependency and the purchase-only policy.
|
||||||
|
- Update constructor/domain JavaDoc and operating documentation and verify the boundaries and recovery paths offline.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-iou-burner-service`: Gate new purchases on the existing reference-price API while keeping reconciliation and burns independent.
|
||||||
|
- `nenjim-hub-configuration`: Supply the corresponding Production status service to the burner using existing explicit dependency ordering.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The burner constructor gains an `EvelynStatusService` dependency, supplied by `NenjimHubImpl`. Runtime input configuration, schedule, Ticker sources, history, ValueTags and live deployment configuration remain outside this change. Verification uses controlled offline collaborators without financial transactions or permanent test additions.
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: At most one confirmed swap attempt per iteration
|
||||||
|
After selecting a positive exact amount and satisfying the current below-reference price condition, 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, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The production EVE burn minimum SHALL be 0.1 EVE and its retained reserve SHALL be 0.01 EVE, as selected by the owner on 2026-10-06, configured as MoneyAmount values using the canonical EVE UUID. The production Hub SHALL supply its corresponding Production Evelyn status service for current reference-price decisions. 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 exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority, supplies minimum burn 0.1 EVE/reserve 0.01 EVE and the Production status service, and retains the other existing production arguments
|
||||||
|
|
||||||
|
#### Scenario: Production burn threshold
|
||||||
|
- **WHEN** production EVE balance is below 0.11, exactly 0.11, or 0.135 with usable mint precision and fresh price
|
||||||
|
- **THEN** below 0.11 produces no burn, 0.11 burns 0.1 and 0.135 burns 0.125; successful burns retain 0.01 EVE
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: New buybacks require a below-reference market price
|
||||||
|
An otherwise eligible new buyback SHALL require a fresh canonical EVE/USDT observation within the existing maximum price age and a usable canonical EVE/USDT reference price from the injected corresponding Evelyn status service at the current decision instant. Both prices SHALL be positive. Only actual price strictly below reference SHALL permit submission; equality SHALL not qualify. The existing public reference operation SHALL be reused without copying curve calculations or reading stored history.
|
||||||
|
|
||||||
|
#### Scenario: Strict boundary below reference
|
||||||
|
- **WHEN** an input is otherwise eligible and reference price is 15 USDT/EVE
|
||||||
|
- **THEN** market price 14.99 permits the existing buyback, while 15.00 and 15.01 submit no new swap from any input in that iteration
|
||||||
|
|
||||||
|
#### Scenario: Required prices are unusable
|
||||||
|
- **WHEN** market observation is missing, stale, incomplete, unavailable or has an invalid pair/non-positive price, or reference lookup throws, is missing or has an invalid pair/non-positive price
|
||||||
|
- **THEN** no new swap is submitted and a safe diagnostic explains why the purchase was skipped
|
||||||
|
|
||||||
|
#### Scenario: Reference uses the current decision instant
|
||||||
|
- **WHEN** the first usable input is ready for a new swap
|
||||||
|
- **THEN** the corresponding status service is asked for its reference price at the current instant rather than using a sampled history point or the observation timestamp
|
||||||
|
|
||||||
|
### Requirement: Price skips preserve independent burn processing and recovery
|
||||||
|
A rejected buyback price condition SHALL skip purchases only, continue complete EVE-balance processing under the existing independent burn requirements, and SHALL NOT create a suspension. Each subsequent otherwise eligible iteration SHALL re-evaluate prices. Pending-burn priority and read-only pending-swap reconciliation SHALL remain independent of the new-purchase condition; any newly permitted purchase after reconciliation SHALL pass the current price check.
|
||||||
|
|
||||||
|
#### Scenario: Existing EVE burns at or above reference
|
||||||
|
- **WHEN** market price is equal to or above reference and existing EVE satisfies its own reserve, minimum, precision, fresh valuation and transaction requirements
|
||||||
|
- **THEN** no new swap occurs and the existing eligible EVE is burned normally
|
||||||
|
|
||||||
|
#### Scenario: Reference unavailable but burn valuation is usable
|
||||||
|
- **WHEN** reference lookup is unavailable or invalid but existing EVE and its fresh canonical valuation satisfy the burn requirements
|
||||||
|
- **THEN** the purchase is skipped and the independent eligible burn still occurs
|
||||||
|
|
||||||
|
#### Scenario: Next scheduled iteration recovers
|
||||||
|
- **WHEN** one iteration skips a purchase because its prices do not qualify and a later otherwise eligible iteration has usable prices below reference
|
||||||
|
- **THEN** the later iteration can attempt its one new swap without a restart or manual reset
|
||||||
|
|
||||||
|
#### Scenario: Pending swap is resolved above reference
|
||||||
|
- **WHEN** a pending swap resolves read-only at or above reference
|
||||||
|
- **THEN** it is cleared normally, any otherwise eligible new purchase is price-checked and skipped, and existing EVE burn evaluation continues
|
||||||
|
|
||||||
|
#### Scenario: Unresolved swap and pending burn retain priority
|
||||||
|
- **WHEN** a swap remains unresolved or the iteration starts with a pending burn
|
||||||
|
- **THEN** existing transaction-priority rules apply without evaluating a new-purchase price condition or submitting a new swap
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is explicitly selected
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||||
|
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||||
|
|
||||||
|
#### Scenario: Production burner has its reference-price dependency
|
||||||
|
- **WHEN** `evelyn.iou-burner.prod` is reached after Ticker and `evelyn.status.prod`
|
||||||
|
- **THEN** the Hub injects the corresponding Production status service for buyback price decisions
|
||||||
|
|
||||||
|
#### Scenario: Production burner precedes its status dependency
|
||||||
|
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||||
|
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Purchase policy and independent burns
|
||||||
|
|
||||||
|
- [x] 1.1 Require the corresponding Evelyn status service and gate a fully eligible new purchase on fresh canonical actual price strictly below its current reference; verify constructor validation, below/equal/above and unavailable/invalid prices with controlled offline checks.
|
||||||
|
- [x] 1.2 Update constructor/domain JavaDoc and Evelyn operating documentation; verify price skips still burn eligible existing EVE, re-evaluate next iteration and preserve pending burn/swap priority in the same offline checks.
|
||||||
|
|
||||||
|
## 2. Hub composition
|
||||||
|
|
||||||
|
- [x] 2.1 Inject the Production status dependency through the existing Hub guard and document the explicit Ticker/status/burner order; verify production arguments and fail-fast dependency behavior without starting the Hub or editing deployment configuration.
|
||||||
|
|
||||||
|
## 3. Integrated verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile main and existing test sources through normal Gradle/Detag, execute focused offline purchase/burn/recovery checks and relevant prior burner harnesses, and record their final results without adding permanent tests or running live transactions.
|
||||||
|
- [x] 3.2 Validate the scoped OpenSpec change strictly and check whitespace and changed-file scope; deliver completed planning artifacts and verification evidence for review.
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified on 2026-10-06 with `/usr/local/software/java/jfx-25`.
|
||||||
|
|
||||||
|
## Compilation and controlled acceptance checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 8s**. Normal Gradle-integrated Detag and both source compilations executed. The initial sandbox attempt could not create the existing Gradle cache lock outside the workspace; the same compilation passed with approved cache access.
|
||||||
|
- Temporary sources in `/tmp/evelyn-issue94` compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue94`.
|
||||||
|
- `java --enable-preview -cp '/tmp/evelyn-issue94:build/classes/java/main:build/libs/*' Issue94Verification` → **PASS: 459 focused issue #94 assertions**.
|
||||||
|
- The copied `Issue93Verification` → **PASS: 246 burn-policy regression assertions**.
|
||||||
|
- The copied `Issue92Verification` → **PASS: 115 input-minimum regression assertions**.
|
||||||
|
- The copied `Issue91Verification`, restricted to burner checks → **PASS: 80 pending-swap/recovery regression assertions**. Unchanged Solana/Jupiter library checks were not rerun.
|
||||||
|
- Production composition source check verifies the corresponding Production status dependency and preserved input reserves/minima/maxima, EVE burn minimum/reserve, hourly interval, slippage, freshness and finalization arguments.
|
||||||
|
- An INFO/WARN-enabled controlled run verifies purchase-skip reasons for at/above reference, unavailable/invalid reference and unusable market price, without arbitrary reference/Ticker exception messages.
|
||||||
|
|
||||||
|
The **900** controlled assertions invoke the actual burner iteration with offline wallet, chain, swap, Ticker, reference and notification collaborators. No RPC, signing, live financial operation, application/Hub startup or deployment configuration read occurs. The Hub dependency guard is invoked directly without constructing a Hub. No permanent automated tests were added. Prior harness copies only add an always-below reference stub to constructor calls and update #92's expected canonical EVE/USDT lookup count for eligible purchases; original temporary files remain intact.
|
||||||
|
|
||||||
|
The #94 harness covers 14.99/15.00/15.01 against reference 15, numeric equality across scales, a close below-reference decimal, the current reference decision instant instead of observation/history time, complete independent burns and reserve preservation, missing/unavailable/non-positive/null-amount/wrong-pair market and reference data, stale market observations, fresh independent burn valuation after a purchase-price failure, stale burn valuation postponement, next-iteration recovery from every rejection category, input priority and amount limits, below-minimum input fallback, no-eligible-input/direct-EVE-only paths, manual swap suspension and null status validation. It verifies original-signature read-only reconciliation while price is above reference, unresolved retention, definitive success/failure/proven expiry followed by a newly rejected purchase, later below-reference recovery, and simultaneous pending burn/swap priority with no reference lookup or new transaction. PriceObservation itself rejects null observation timestamp/price before they can reach the burner.
|
||||||
|
|
||||||
|
An initial temporary harness compilation used the wrong fully qualified component-ID type; it was corrected to the repository's existing `com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId`. Final compiled checks pass. Concurrent initial Java invocations reported harmless performance-data lock warnings; the controlled diagnostic run disabled performance data. No production fix was needed for either verification-only issue.
|
||||||
|
|
||||||
|
## Review and workflow
|
||||||
|
|
||||||
|
Parent independent review of the proposal/specs/design, implementation, operating documentation and controlled harness found no actionable findings. The existing malformed burn JavaDoc line was corrected while updating that same list item. Scope is limited to the required reference dependency, new-purchase condition, Hub composition, related documentation and two OpenSpec capability deltas. Transaction execution, reconciliation, burn valuation/policy, ValueTags, scheduling and private deployment files retain their existing behavior.
|
||||||
|
|
||||||
|
Strict validation of the active change and `git diff --check` pass. All four planning artifact types and all five implementation tasks are complete. The active change is ready for review; main specifications have not been synchronized and the change has not been archived.
|
||||||
|
|
||||||
|
No deployment, commit, push, Gitea write or live integration was performed.
|
||||||
|
|
||||||
|
## Follow-up: clearer purchase-skip logs
|
||||||
|
|
||||||
|
The owner's requested logging refinement keeps price eligibility unchanged. INFO price skips now show a direct labelled comparison, for example `Skipping buyback as EVE price is at or above reference (market=15.01 >= reference=15 USDT/EVE)`, followed by the trading pair, decision instant and next action. Prices use plain decimals without redundant trailing zeros. WARN skips retain their reason; unavailable or invalid reference-price diagnostics also include the known market price.
|
||||||
|
|
||||||
|
`JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava --offline --no-daemon` passed. The existing temporary #94 harness passed its 459 assertions again with INFO/WARN enabled. Its captured output in `/tmp/evelyn-issue94/issue94-logging-refinement.log` confirms the numeric comparison for both equality and higher prices, reference failure details, missing/stale market diagnostics and exclusion of arbitrary exception messages. No new tests were added.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-19
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation. EMC currently constructs matching placeholder tabs under Production and Test in `EvelynMissionControlImpl`. JavaFX and its shared runtime are already available.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Isolate the prototype's construction and sample data inside the EMC implementation package; keep the existing environment tab colors.
|
||||||
|
|
||||||
|
**Non-Goals:** Public position APIs, domain/service models, runtime changes, refresh scheduling, or configuration.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Add a package-private Perps tab builder instead of growing the chart-oriented main implementation or introducing service abstractions. Each environment gets a fresh tree, so collapsing one view does not affect the other.
|
||||||
|
- Use JavaFX's native tree table and an invisible expanded root. A private display record holds a label, optional direction, and optional USD size using the existing `ΩAmountΩ` ValueTag (`BigDecimal`). This is a monetary amount expressed in USD by the display contract, not a USDC token amount or Jupiter-specific raw integer. No Detag configuration changes are needed.
|
||||||
|
- Format the numeric size as US currency in the display layer, align it right, and leave group values blank. Disable column sorting to preserve the sample grouping and position order.
|
||||||
|
- Apply faint background colors through the row factory and reset the style when a virtualized row becomes empty or represents a group. Keep native selection styling and readable text by setting the control's inner background colors rather than overriding selected-row backgrounds.
|
||||||
|
- Put a wrapping sample-data notice above the table, including the illustrative status of Phoenix. Use a growing layout so the table follows the available window size.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Reused rows could retain a position color after collapse or scrolling] → Clear inline styling for groups and empty rows and check it in UI validation.
|
||||||
|
- [Samples could be mistaken for real positions in Production] → Show the explicit sample notice in both environments.
|
||||||
|
- [No graphical display may be available in the execution environment] → Attempt isolated UI verification without starting the operational Hub; record any remaining visual checks honestly.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Evelyn Mission Control's Perps tab is empty. Issue #95 requests a small local prototype so the owner can evaluate the active-position hierarchy before connecting Evelyn services.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Display an expanded, collapsible wallet → venue → position tree table in the Perps tab.
|
||||||
|
- Include hardcoded Jupiter and Phoenix examples with Long/Short text, readable USD sizes, and faint direction colors on position rows only.
|
||||||
|
- Clearly identify sample data and illustrative Phoenix rows; add no Refresh button, service communication, or simulated updates.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `evelyn-perps-overview`: EMC's hierarchical overview of perpetual positions, initially displayed with explicitly labeled sample data.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Only EMC implementation code under `com.r35157.evelyn.emc.impl.ref` and this OpenSpec change. Both Production and Test Perps tabs receive independent sample views. No public API, service, configuration, dependency, or network changes.
|
||||||
|
|
||||||
|
Source: https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/95
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Let the owner inspect Evelyn perpetual positions grouped by wallet and venue in Mission Control, beginning with a clearly labeled sample-data prototype.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Perps overview displays a collapsible position hierarchy
|
||||||
|
The Perps tab SHALL display a tree table with columns `Position`, `Direction`, and `Position size, USD`. The hierarchy SHALL be wallet → venue → position, expanded by default, with collapsible wallet and venue groups. Wallet and venue rows SHALL leave direction and size empty.
|
||||||
|
|
||||||
|
#### Scenario: Open either environment's Perps tab
|
||||||
|
- **WHEN** the owner opens Perps in Production or Test
|
||||||
|
- **THEN** the three named columns and the fully expanded hierarchy are visible
|
||||||
|
- **AND** direction and size appear only on position rows
|
||||||
|
|
||||||
|
#### Scenario: Collapse and expand groups
|
||||||
|
- **WHEN** the owner collapses and then expands a wallet or venue
|
||||||
|
- **THEN** its descendants are hidden and then shown again
|
||||||
|
|
||||||
|
### Requirement: Sample positions communicate direction and USD size
|
||||||
|
The overview SHALL include hardcoded Jupiter and Phoenix venues with multiple sample positions, including both Long and Short directions. Position rows SHALL show `Long` or `Short` text and USD sizes with grouping separators and two decimal places. Long rows SHALL have faint green backgrounds and Short rows faint red backgrounds with readable text; wallet and venue rows SHALL have no direction-based color.
|
||||||
|
|
||||||
|
#### Scenario: Inspect sample positions
|
||||||
|
- **WHEN** the owner inspects Jupiter and Phoenix examples
|
||||||
|
- **THEN** both venues have multiple positions and both directions are demonstrated
|
||||||
|
- **AND** position sizes use readable USD formatting
|
||||||
|
- **AND** only position rows use direction-based backgrounds, with direction also conveyed by text
|
||||||
|
|
||||||
|
### Requirement: Prototype is clearly labeled and entirely local
|
||||||
|
The Perps view SHALL explicitly label its data as sample data and explain that Phoenix rows are illustrative without an existing integration. It SHALL remain entirely within EMC, using no network calls, service changes, service communication, Refresh button, or simulated live updates for the prototype.
|
||||||
|
|
||||||
|
#### Scenario: Inspect the prototype boundary
|
||||||
|
- **WHEN** the Perps tab is displayed
|
||||||
|
- **THEN** sample-data and illustrative-Phoenix labels are visible
|
||||||
|
- **AND** there is no Refresh button or automated update of sample positions
|
||||||
|
- **AND** displaying and interacting with the prototype uses only local EMC data
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Local Perps prototype
|
||||||
|
|
||||||
|
- [x] 1.1 Implement the EMC-local expanded wallet/venue/position table, sample notice, USD formatting, and direction colors; verify source against issue #95 including blank group cells and row-style reset.
|
||||||
|
- [x] 1.2 Replace both environments' Perps placeholders with independent sample views; verify integration and compile with the normal Gradle build without starting the operational Hub.
|
||||||
|
|
||||||
|
## 2. Integration verification
|
||||||
|
|
||||||
|
- [x] 2.1 Validate this OpenSpec change strictly and run existing Gradle checks; record results and a visual expand/collapse, selection, and row-reuse check or any unavailable graphical verification in verification.md.
|
||||||
|
- [x] 2.2 Review the final diff for EMC-only implementation scope, absence of network/service changes and Refresh controls, and clean whitespace; leave all changes uncommitted for owner review.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified 2026-10-19 against Gitea issue #95.
|
||||||
|
|
||||||
|
## Build and existing checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew build` — passed, including normal Detag generation, compilation, packaging, and existing tests.
|
||||||
|
- Existing suite: 42 tests across six suites, zero failures, errors, or skips. No new automated tests were added.
|
||||||
|
- Repeated build after switching the display amount to the existing `ΩAmountΩ` tag — passed; generated Java remained identical and compilation/tests were up-to-date.
|
||||||
|
- `openspec validate 95-display-perps-sample-positions --strict` — passed.
|
||||||
|
- `git diff --check` — passed.
|
||||||
|
|
||||||
|
## Isolated JavaFX inspection
|
||||||
|
|
||||||
|
A temporary preview outside the repository constructed the actual `PerpsTab` in nested Production/Test tabs in a 600 × 400 JavaFX scene, rendered snapshots, changed expansion/selection, and scrolled the table. The operational Hub and its services were not started.
|
||||||
|
|
||||||
|
- Inspected expanded, selected, scrolled, collapsed-group, and Test snapshots. Column headings and USD values are readable; the notice wraps without clipping and identifies Phoenix as illustrative.
|
||||||
|
- Eight positions appear under two wallets and four venue groups (14 visible tree rows when expanded). Both venues have multiple Long/Short samples.
|
||||||
|
- Position rows are faint green/red. Selected rows retain native selection styling and readable direction text.
|
||||||
|
- Collapsing wallet A reduces visible rows from 14 to eight; Test remains at 14 rows, confirming independent trees.
|
||||||
|
- Collapsing all venues leaves six group rows with blank direction/size cells and no residual direction colors after row reuse.
|
||||||
|
- Scrolling exposes the remaining positions with correct colors, directions, and sizes.
|
||||||
|
|
||||||
|
This verifies rendered scenes and programmatic control behavior. Mouse/keyboard operation in the running EMC application was not exercised.
|
||||||
|
|
||||||
|
## Scope review
|
||||||
|
|
||||||
|
Implementation consists of the new package-private EMC `PerpsTab` and one placeholder replacement in `EvelynMissionControlImpl`. Source review confirms no Refresh control, network calls, service changes, sample refresh timeline, public API changes, configuration changes, or dependency changes. Changes remain local and uncommitted for owner review; the OpenSpec change remains active.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-19
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See proposal.md for motivation. `PerpsTab` currently builds all sample rows. EMC uses a JavaFX Timeline for chart refresh and stops it on hide. The Hub explicitly constructs, registers, starts and records selected processes in configuration order.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Separate domain data from presentation; preserve UI identity; make polling independent of any future integration cadence.
|
||||||
|
|
||||||
|
**Non-Goals:** Exchange dependencies, a data-update worker, trading, changing samples, additional UI controls or a new general lifecycle framework.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Put `EvelynPerpsService`, snapshot records and direction in `com.r35157.evelyn.service.perps`, with implementation in `impl.ref`. Nested wallet/venue/position records use IDs separate from display labels and defensive list copies. Use the existing Amount ValueTag for decimal USD size. UI labels/colors remain in EMC. A UI-shaped response would couple the service to JavaFX; a flat response would duplicate grouping responsibility.
|
||||||
|
- Use one `evelyn.perps.default` process for the unchanged examples shown in both environments. Its start publishes the immutable snapshot, reads before start fail, and a second start fails. No worker is needed for static in-memory data. Separate prod/test instances would add configuration without a current difference in data.
|
||||||
|
- Use a single daemon scheduled worker per visible EMC session, requesting immediately and every three seconds. Reads execute sequentially off JavaFX; completion is dispatched through the injected JavaFX runtime. Hide cancels the worker, and a session token rejects queued obsolete results after reopening. Exceptions are logged and isolated so polling continues. Reuse the existing chart lifecycle unchanged.
|
||||||
|
- Retain a Perps view per environment. Skip equal snapshots completely. Reconcile wallet/venue/position tree items by IDs within their parent, updating only changed row values and child order, preserving retained expansion and selection. Do not rebuild or call table refresh for unchanged data.
|
||||||
|
- Add the service to the Hub catalogue and require it in EMC construction. Update the ignored deployment configuration before EMC; retain the minimal tracked bootstrap example and document the migration.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Late results after hide → cancel the session worker and check its identity on the JavaFX thread before applying.
|
||||||
|
- A slow read → sequential worker prevents overlapping polling calls; current snapshot reads have no I/O.
|
||||||
|
- Runtime configuration is ignored → document the required ID and order for other deployments.
|
||||||
|
- Static samples cannot prove real integration correctness → verify only the requested boundary, appearance and polling behavior.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Build and validate offline, then run an isolated JavaFX verification without starting the live Hub or financial services. On the next ordinary Hub startup, the modified local autostart list starts Perps before EMC. Other deployments must add the same entry explicitly. Rollback restores the previous code and removes that entry together. Leave the change ready for review, without commit, push, deployment or automatic archive.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Issue #96 moves ownership of Evelyn's existing Perps sample positions out of EMC into a view-only service. This establishes the data boundary for Evelyn's position overview while preserving its current appearance and avoiding exchange integration work.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Introduce `EvelynPerpsService` with an immutable, consistently identified wallet → venue → position snapshot and a separate reference implementation using the Hub process lifecycle.
|
||||||
|
- Move all existing hardcoded positions into that service, without network access or position-management operations.
|
||||||
|
- Poll the snapshot off the JavaFX thread every three seconds while EMC is visible; retain tree items, expansion and selection when data is unchanged.
|
||||||
|
- Register `evelyn.perps.default`, require it before EMC, and add it to this deployment's ordered autostart configuration.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `evelyn-perps-service`: View-only, in-memory Perps snapshots with stable domain identifiers and Hub-owned startup.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-perps-overview`: Replace EMC-owned samples with asynchronous lifecycle-bound polling while retaining presentation and view state.
|
||||||
|
- `nenjim-hub-configuration`: Support the Perps process and explicitly ordered injection into EMC.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Changes the Evelyn service API, EMC constructor and Perps presentation, Hub composition, local ignored runtime configuration, and documentation. No external dependencies, trades, real exchange reads, simulated changes, new controls, commits or pushes.
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Prototype is clearly labeled and entirely local
|
||||||
|
The Perps view SHALL explicitly label its data as sample data and explain that Phoenix rows are illustrative without an existing integration. It SHALL obtain positions only from the Perps service's current in-memory snapshot, with no external network calls, Refresh button, or simulated position changes. EMC SHALL own all UI components, row colors and formatting.
|
||||||
|
|
||||||
|
#### Scenario: Inspect the prototype boundary
|
||||||
|
- **WHEN** the Perps tab is displayed
|
||||||
|
- **THEN** sample-data and illustrative-Phoenix labels are visible
|
||||||
|
- **AND** there is no Refresh button or simulated update of sample positions
|
||||||
|
- **AND** displaying and interacting with the prototype uses only the service's local sample snapshot
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Asynchronous polling follows window visibility
|
||||||
|
EMC SHALL request the current Perps snapshot immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||||
|
|
||||||
|
#### Scenario: Read takes time or fails
|
||||||
|
- **WHEN** a snapshot read is delayed or fails
|
||||||
|
- **THEN** the UI remains responsive and the existing view is retained
|
||||||
|
- **AND** later polling remains possible without overlapping scheduled reads
|
||||||
|
|
||||||
|
#### Scenario: Hide and reopen
|
||||||
|
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||||
|
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||||
|
|
||||||
|
### Requirement: Polling preserves Perps view state
|
||||||
|
The Perps table SHALL retain matching wallet, venue and position identities across snapshots. Repeated unchanged snapshots SHALL leave existing rows, expansion, selection and formatting untouched without flicker. New groups SHALL be expanded by default, and future added or removed positions SHALL be reconciled by identity without resetting retained groups.
|
||||||
|
|
||||||
|
#### Scenario: Poll unchanged positions
|
||||||
|
- **WHEN** a user collapses a group or selects a position and repeated snapshots are unchanged
|
||||||
|
- **THEN** the same rows remain, with the same expansion and selection and no rebuild or refresh
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide a view-only source of current Evelyn perpetual positions independently of Mission Control presentation and refresh timing.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Read-only current positions snapshot
|
||||||
|
The Perps service SHALL expose only a read operation returning its complete current in-memory snapshot. Wallets, venues within wallets, and positions within venues SHALL have stable identifiers across reads. Responses SHALL be immutable domain data without UI components, colors or formatting, and SHALL make no Jupiter or Phoenix requests.
|
||||||
|
|
||||||
|
#### Scenario: Repeated reads
|
||||||
|
- **WHEN** clients repeatedly retrieve the started service's snapshot
|
||||||
|
- **THEN** they receive the same complete positions with stable identifiers and cannot mutate service-owned collections
|
||||||
|
- **AND** no exchange requests or position-management operations occur
|
||||||
|
|
||||||
|
### Requirement: Service owns the unchanged sample positions
|
||||||
|
The service SHALL own the existing eight sample positions grouped under Sample wallet A and Sample wallet B and their Jupiter and Phoenix venues, retaining every existing instrument, direction and decimal USD size. The snapshot SHALL remain unchanged for this task.
|
||||||
|
|
||||||
|
#### Scenario: Existing samples are retrieved
|
||||||
|
- **WHEN** EMC retrieves the service snapshot
|
||||||
|
- **THEN** wallet A contains Jupiter SOL Long 12500.00 and BTC Short 8250.50, Phoenix SOL Short 3200.75 and ETH Long 5400.00
|
||||||
|
- **AND** wallet B contains Jupiter ETH Long 1875.25 and SOL Short 950.00, Phoenix BTC Long 21000.00 and SOL Short 640.50
|
||||||
|
|
||||||
|
### Requirement: Hub owns Perps startup
|
||||||
|
The service SHALL run as a separate Hub-managed process registered as `evelyn.perps.default`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||||
|
|
||||||
|
#### Scenario: Service starts
|
||||||
|
- **WHEN** the Hub starts the registered Perps process
|
||||||
|
- **THEN** its service view makes the complete sample snapshot available without starting a polling or integration worker
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.default`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.default` service, which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Renamed status services retain dependency wiring
|
||||||
|
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||||
|
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||||
|
|
||||||
|
#### Scenario: Deployment still contains an old status ID
|
||||||
|
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||||
|
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||||
|
|
||||||
|
#### Scenario: Liquidity is explicitly selected
|
||||||
|
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||||
|
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||||
|
|
||||||
|
#### Scenario: Production burner has its reference-price dependency
|
||||||
|
- **WHEN** `evelyn.iou-burner.prod` is reached after Ticker and `evelyn.status.prod`
|
||||||
|
- **THEN** the Hub injects the corresponding Production status service for buyback price decisions
|
||||||
|
|
||||||
|
#### Scenario: Production burner precedes its status dependency
|
||||||
|
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||||
|
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||||
|
|
||||||
|
#### Scenario: EMC has its Perps dependency
|
||||||
|
- **WHEN** `evelyn.perps.default` appears before EMC in the deployment's ordered autostart list
|
||||||
|
- **THEN** the Hub registers and starts that service and injects its service view into EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC precedes Perps
|
||||||
|
- **WHEN** EMC is selected without an earlier Perps process
|
||||||
|
- **THEN** construction fails clearly without inserting or starting that dependency
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Perps service and data boundary
|
||||||
|
|
||||||
|
- [x] 1.1 Add the domain-only immutable snapshot API and Hub-started reference service; verify all eight existing values, stable IDs, read-only API and defensive list copies by source inspection and compilation.
|
||||||
|
- [x] 1.2 Document ownership and lifecycle in docs/Evelyn.md; verify documentation matches the public API and no exchange dependencies are introduced.
|
||||||
|
|
||||||
|
## 2. EMC polling and presentation
|
||||||
|
|
||||||
|
- [x] 2.1 Replace EMC-owned samples with identity-based snapshot rendering; verify unchanged snapshots leave tree items, expansion and selection untouched, and formatting/colors remain unchanged.
|
||||||
|
- [x] 2.2 Inject the service and add sequential off-thread three-second polling tied to window visibility; verify initial retrieval, error recovery, hide/reopen cancellation and obsolete-result rejection in an isolated JavaFX session.
|
||||||
|
|
||||||
|
## 3. Hub startup wiring
|
||||||
|
|
||||||
|
- [x] 3.1 Add the process catalogue entry and required EMC dependency, and update local autostart before EMC; verify actual configuration parses and the same service object has both service/process views.
|
||||||
|
- [x] 3.2 Document the new process ID and explicit ordering in docs/Nenjim.md; verify the tracked minimal bootstrap example remains unchanged.
|
||||||
|
|
||||||
|
## 4. Integration verification
|
||||||
|
|
||||||
|
- [x] 4.1 Run the normal Gradle build, strict OpenSpec validation and diff review; record results and leave all work uncommitted and unpushed with the active change ready for review.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Verification
|
||||||
|
|
||||||
|
Verified 2026-10-19 against Gitea issue #96 and the owner's explicit autostart requirement.
|
||||||
|
|
||||||
|
## Build and specification checks
|
||||||
|
|
||||||
|
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew build` passed, including normal Detag processing, compilation, packaging and the existing 42 tests across six suites, with zero failures, errors or skips.
|
||||||
|
- `openspec validate 96-extract-evelyn-perps-service --strict` passed.
|
||||||
|
- `git diff --check` passed.
|
||||||
|
- No automated tests were added to the repository. The runtime checks below used a temporary standalone preview under `/tmp/emc96-preview`.
|
||||||
|
|
||||||
|
## Service and Hub verification
|
||||||
|
|
||||||
|
- Snapshot reads before startup and duplicate startup fail clearly. The started service returns its complete fixed immutable snapshot; wallet, venue and position collections reject mutation.
|
||||||
|
- Inspected all eight original instruments, directions and decimal USD values and stable sibling IDs. The public service API has only one snapshot read operation. Its response types contain no JavaFX, presentation formatting or exchange dependencies.
|
||||||
|
- Parsed the actual ignored `conf/NenjimHub.conf` against the Hub's constructor catalogue. It includes `evelyn.perps.default` before EMC.
|
||||||
|
- Exercised the Hub's normal `constructRegisterAndStartProcess` path for only Perps with an isolated Registry. The same object resolved as both `EvelynPerpsService` and `NenjimProcess`, returned a populated snapshot and appeared in the Hub's successful-start snapshot.
|
||||||
|
- The tracked `conf/NenjimHub.conf.example` is unchanged. No operational Hub, wallet signer, financial process or exchange integration was started.
|
||||||
|
|
||||||
|
## Isolated JavaFX runtime inspection
|
||||||
|
|
||||||
|
Constructed the actual EMC with empty status-service views, the real Perps sample service behind an instrumented read-only view, and the real JavaFX runtime. All instrumented snapshot calls ran outside the JavaFX thread.
|
||||||
|
|
||||||
|
- Initial retrieval populated both environments with two wallets, four venues and eight positions: 14 expanded rows.
|
||||||
|
- Measured the first two scheduled gaps as 2.999846105 and 2.999953679 seconds.
|
||||||
|
- Collapsed wallet A and selected a position in wallet B. Repeated polls preserved the same selection and expansion. List/property listeners observed zero tree mutations for unchanged data. Test remained independently expanded.
|
||||||
|
- Inspected rendered images for the original column headings, sample/Phoenix notice, USD formatting, faint Long/Short colors and selection styling.
|
||||||
|
- Injected one read failure; the following scheduled read recovered and polling continued.
|
||||||
|
- Blocked one read and successfully dispatched UI work while it waited; hiding the window interrupted the blocked read and stopped subsequent polling.
|
||||||
|
- Reopened EMC and confirmed retained selection. Held an old session's queued completion across hide, emptied the preview's view, then delivered that obsolete completion; it did not repopulate the view.
|
||||||
|
- Reopened again and issued repeated start requests while visible. Exactly two additional reads occurred over 6.4 seconds, demonstrating one three-second loop rather than accumulated loops.
|
||||||
|
|
||||||
|
Images are temporary local evidence in `/tmp/emc96-preview/`: `expanded.png`, `collapsed-selected.png`, `after-polling.png` and `reopened.png`. Inspection exercised actual JavaFX scenes and controls; manual mouse/keyboard use in the operational deployment was not exercised.
|
||||||
|
|
||||||
|
## Review state
|
||||||
|
|
||||||
|
All work remains local, uncommitted and unpushed. The OpenSpec change remains active for owner review; its delta specs have not been archived into the main specs. The new service will autostart on the next normal Hub startup using this deployment's updated local configuration.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-22
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
See proposal.md. The service currently creates two named sample wallets internally; EMC uses one shared service and snapshot for both tabs. Hub startup is explicitly ordered.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
Keep wallet configuration out of EMC and snapshots instance-owned. No real addresses, integrations, trading, new configuration framework or UI redesign are introduced.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
- Add an immutable wallet configuration record (stable ID and display name), supplied as a defensively copied list to the implementation constructor. The sample IDs are not Solana addresses. This avoids passing UI data or moving sample position definitions into Hub.
|
||||||
|
- Hub owns one immutable list of the two existing sample wallet identities, supplied to both independently constructed processes.
|
||||||
|
- Keep the existing two sample position sets in the service and assign them by configured wallet order, alternating for additional sample wallets. This is explicitly sample-only behavior, not an integration policy; empty configuration gives an empty snapshot. It avoids coupling sample data to wallet IDs or requiring exactly two wallets.
|
||||||
|
- Inject PROD and TEST service views into EMC. Retain one off-thread scheduled loop, with separate reads and error handling for each tab. Preserve existing rendering and session guards.
|
||||||
|
- Retire the default ID rather than aliasing it, following explicit process selection and fail-fast dependencies.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
- Identical fixtures conceal crossed wiring → verify routing by source inspection and an isolated session with distinct snapshots.
|
||||||
|
- Private startup configuration is ignored by Git → update the local file and document the required deployment migration.
|
||||||
|
- Sample assignment is ordinal → document this temporary fixture behavior; real integration remains a later task.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
Replace evelyn.perps.default with evelyn.perps.prod and evelyn.perps.test before EMC. Both must precede EMC. The minimal tracked bootstrap template stays unchanged. Rollback requires restoring both code and the old configuration entry. Do not start production processes during verification.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
EMC currently shares one Perps service between TEST and PROD. Issue #97 requires separate service ownership and Hub-supplied wallet configuration before real integrations are introduced.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
- **BREAKING**: Replace `evelyn.perps.default` with `evelyn.perps.test` and `evelyn.perps.prod` in the process catalogue and startup configuration.
|
||||||
|
- Pass the same hardcoded wallet configuration from NenjimHub to two independent service instances.
|
||||||
|
- Poll each instance for its corresponding EMC tab every three seconds, preserving presentation and the parameterless snapshot API.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `evelyn-perps-service`: Hub-configured wallets and separate TEST/PROD lifecycles and snapshots.
|
||||||
|
- `nenjim-hub-configuration`: Separate Perps process IDs and explicit EMC dependencies.
|
||||||
|
- `evelyn-perps-overview`: Each environment tab polls its own service.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
Perps service construction, EMC injection and polling, Hub process catalogue, local startup configuration and project documentation. No exchange integration, trading, advanced configuration or UI changes.
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Asynchronous polling follows window visibility
|
||||||
|
EMC SHALL request the current Perps snapshot from each environment's own service immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||||
|
|
||||||
|
#### Scenario: Read takes time or fails
|
||||||
|
- **WHEN** a snapshot read is delayed or fails
|
||||||
|
- **THEN** the UI remains responsive and the existing view is retained
|
||||||
|
- **AND** later polling remains possible without overlapping scheduled reads
|
||||||
|
|
||||||
|
#### Scenario: Hide and reopen
|
||||||
|
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||||
|
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||||
|
|
||||||
|
#### Scenario: Environment data stays separate
|
||||||
|
- **WHEN** TEST and PROD return different snapshots
|
||||||
|
- **THEN** each tab displays only its own service's data
|
||||||
|
- **AND** a failure reading one service does not prevent attempting the other service
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user