Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f6f6b72fb | ||
|
|
734de67c54 |
+2
-7
@@ -6,13 +6,8 @@ build
|
|||||||
|
|
||||||
logs/*.log
|
logs/*.log
|
||||||
logs/*.log.gz
|
logs/*.log.gz
|
||||||
|
conf/*.conf
|
||||||
# Ignore all files under conf
|
conf/*.xml
|
||||||
conf/**
|
|
||||||
# Except all subdirectories so git will search all those
|
|
||||||
!conf/**/
|
|
||||||
# But do NOT ignore those 'example' files
|
|
||||||
!conf/**/*.example
|
|
||||||
|
|
||||||
# Ignore all files under data
|
# Ignore all files under data
|
||||||
data/**
|
data/**
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ configuration files. Compatible additions do not require a version increment.
|
|||||||
|
|
||||||
This policy currently applies to:
|
This policy currently applies to:
|
||||||
|
|
||||||
- `NenjimHub.conf` (mandatory version 1 with ordered `[AUTO_START]`)
|
- `NenjimHub.conf` (mandatory version 2 with ordered `[AUTO_START]`)
|
||||||
- `alarms.conf`
|
- `alarms.conf`
|
||||||
- `alarmaction_Console.conf`
|
- `alarmaction_Console.conf`
|
||||||
- `alarmaction_Pushover.conf`
|
- `alarmaction_Pushover.conf`
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ val detagMain = tasks.register<JavaExec>("detagMain") {
|
|||||||
classpath = detag
|
classpath = detag
|
||||||
mainClass.set("com.r35157.tools.detag.impl.ref.Main")
|
mainClass.set("com.r35157.tools.detag.impl.ref.Main")
|
||||||
|
|
||||||
val configFile = layout.projectDirectory.file("conf/detag.conf")
|
val configFile = layout.projectDirectory.file("../detag.conf")
|
||||||
val sourceRoot = layout.projectDirectory.dir("src/main/tjava")
|
val sourceRoot = layout.projectDirectory.dir("src/main/tjava")
|
||||||
|
|
||||||
inputs.file(configFile)
|
inputs.file(configFile)
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
# Copy this template to conf/NenjimHub.conf before starting Nenjim Hub.
|
# Copy this template to conf/NenjimHub.conf before starting Nenjim Hub.
|
||||||
# Registry and Journal are mandatory and must be the first two entries.
|
# Registry and Journal are mandatory and must be the first two entries.
|
||||||
# Entries are ordered. List every process dependency manually before its consumers.
|
# Entries are ordered. List every process dependency manually before its consumers.
|
||||||
|
# The JavaFX runtime must precede JavaFX applications.
|
||||||
|
# Commenting out an entry prevents that process from being constructed or started.
|
||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
[AUTO_START]
|
[AUTO_START]
|
||||||
nenjim.registry.service
|
nenjim.registry.service
|
||||||
nenjim.journal.service
|
nenjim.journal.service
|
||||||
|
#assetaz.ticker.default
|
||||||
|
#evelyn.service.prod
|
||||||
|
#evelyn.service.test
|
||||||
|
#evelyn.iou-burner.prod
|
||||||
|
#nenjim.javafx-runtime.service
|
||||||
|
#evelyn.mission-control.default
|
||||||
|
#nenjim.process-manager.default
|
||||||
|
|
||||||
|
|||||||
+9
-17
@@ -23,7 +23,7 @@
|
|||||||
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
||||||
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
||||||
<code>JavaFxRuntimeService</code>, the first central component Registry, and mandatory Hub configuration
|
<code>JavaFxRuntimeService</code>, the first central component Registry, and mandatory Hub configuration
|
||||||
format 1 are implemented. Contexts, resolution, classloading, discovery, runtime loading/removal,
|
format 2 are implemented. Contexts, resolution, classloading, discovery, runtime loading/removal,
|
||||||
downloading, publishing, synchronization, watching, signatures, permissions, and license filtering remain
|
downloading, publishing, synchronization, watching, signatures, permissions, and license filtering remain
|
||||||
future work.</p>
|
future work.</p>
|
||||||
|
|
||||||
@@ -148,17 +148,20 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
|||||||
Registry manager. That manager exposes only registration, is neither a component nor a process, is not
|
Registry manager. That manager exposes only registration, is neither a component nor a process, is not
|
||||||
registered, and cannot start anything. The internal administration interface must remain package-private so
|
registered, and cannot start anything. The internal administration interface must remain package-private so
|
||||||
an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
|
an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
|
||||||
<h3>Mandatory NenjimHub.conf format 1</h3>
|
<h3>Mandatory NenjimHub.conf format 2</h3>
|
||||||
<p><code>NenjimHubImpl</code> is the global composition and runtime owner. Before any process starts it reads only
|
<p><code>NenjimHubImpl</code> is the global composition and runtime owner. Before any process starts it reads only
|
||||||
the readable regular file <code>conf/NenjimHub.conf</code>. The version-controlled
|
the readable regular file <code>conf/NenjimHub.conf</code>. The version-controlled
|
||||||
<code>conf/NenjimHub.conf.example</code> is a copy-only template: the application never reads, copies,
|
<code>conf/NenjimHub.conf.example</code> is a copy-only template: the application never reads, copies,
|
||||||
generates, or falls back to it or to a hardcoded startup sequence.</p>
|
generates, or falls back to it or to a hardcoded startup sequence.</p>
|
||||||
<pre><code>FORMAT_VERSION=1
|
<pre><code>FORMAT_VERSION=2
|
||||||
|
|
||||||
[AUTO_START]
|
[AUTO_START]
|
||||||
nenjim.registry.service
|
nenjim.registry.service
|
||||||
nenjim.journal.service</code></pre>
|
nenjim.journal.service
|
||||||
<p><code>FORMAT_VERSION=1</code> must be the first actual entry and the one exact
|
assetaz.ticker.default
|
||||||
|
evelyn.service.prod
|
||||||
|
evelyn.iou-burner.prod</code></pre>
|
||||||
|
<p><code>FORMAT_VERSION=2</code> must be the first actual entry and the one exact
|
||||||
<code>[AUTO_START]</code> header must be the next actual entry. Leading and trailing whitespace is ignored.
|
<code>[AUTO_START]</code> header must be the next actual entry. Leading and trailing whitespace is ignored.
|
||||||
A trimmed line beginning with <code>#</code> is a comment, and <code>#</code> after content begins a trailing
|
A trimmed line beginning with <code>#</code> is a comment, and <code>#</code> after content begins a trailing
|
||||||
comment. Inside the section a comment-only line does not terminate it, while the first whitespace-only line
|
comment. Inside the section a comment-only line does not terminate it, while the first whitespace-only line
|
||||||
@@ -168,9 +171,7 @@ nenjim.journal.service</code></pre>
|
|||||||
duplicates, invalid or unknown IDs, passive-only IDs, and IDs the Hub cannot construct as modern processes
|
duplicates, invalid or unknown IDs, passive-only IDs, and IDs the Hub cannot construct as modern processes
|
||||||
fail. Registry must occur exactly once first and Journal exactly once second. The known selectable processes
|
fail. Registry must occur exactly once first and Journal exactly once second. The known selectable processes
|
||||||
are Registry, Journal, Ticker, Jupiter Perps Alarm, both Evelyn services, Production IOU Burner, JavaFX
|
are Registry, Journal, Ticker, Jupiter Perps Alarm, both Evelyn services, Production IOU Burner, JavaFX
|
||||||
runtime, Mission Control, and Process Manager. The version-controlled template selects only the mandatory
|
runtime, Mission Control, and Process Manager. The default template omits the alarm.</p>
|
||||||
Registry and Journal processes; every other process must be added explicitly to the private deployment
|
|
||||||
configuration.</p>
|
|
||||||
<p>After validation the Hub starts, registers, and records Registry, then eagerly constructs and registers the
|
<p>After validation the Hub starts, registers, and records Registry, then eagerly constructs and registers the
|
||||||
complete passive graph without activating a process. It next visits every remaining configured ID in exact
|
complete passive graph without activating a process. It next visits every remaining configured ID in exact
|
||||||
order, beginning with Journal. Each selected process is constructed only when reached, registered before
|
order, beginning with Journal. Each selected process is constructed only when reached, registered before
|
||||||
@@ -181,15 +182,6 @@ nenjim.journal.service</code></pre>
|
|||||||
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>
|
||||||
<p>Ordinary consumers receive the lifecycle-free <code>TickerService</code> view, which exposes only the
|
|
||||||
latest-price query. The same <code>TickerServiceImpl</code> object separately implements
|
|
||||||
<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
|
|
||||||
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>
|
||||||
|
|||||||
+9
-17
@@ -3,7 +3,7 @@
|
|||||||
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
||||||
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
||||||
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService`, første version af det centrale component Registry
|
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService`, første version af det centrale component Registry
|
||||||
> og den obligatoriske format 1 Hub-konfiguration er implementeret. Contexts, resolution, discovery, runtime loading
|
> og den obligatoriske format 2 Hub-konfiguration er implementeret. Contexts, resolution, discovery, runtime loading
|
||||||
> og removal er fortsat fremtidig funktionalitet.
|
> og removal er fortsat fremtidig funktionalitet.
|
||||||
|
|
||||||
**Indholdsfortegnelse:**
|
**Indholdsfortegnelse:**
|
||||||
@@ -199,16 +199,6 @@ kan starte og tracke, implementerer desuden den canonical `com.r35157.nenjim.pro
|
|||||||
`start()` og ikke en fælles stop-, restart- eller health-operation. En service- eller applikationsimplementation kan
|
`start()` og ikke en fælles stop-, restart- eller health-operation. En service- eller applikationsimplementation kan
|
||||||
derfor også være en process; passive componenter er det ikke.
|
derfor også være en process; passive componenter er det ikke.
|
||||||
|
|
||||||
Ticker følger denne opdeling: almindelige consumers modtager `TickerService` med kun latest-price query, mens det
|
|
||||||
samme `TickerServiceImpl`-objekt separat er en `NenjimProcess`, som Hub'en starter. Tickerens source-lifecycle er
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
||||||
@@ -246,13 +236,13 @@ fordi almindelige consumers ellers kunne caste deres Registry-service til mutati
|
|||||||
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
||||||
registration efter startup.
|
registration efter startup.
|
||||||
|
|
||||||
### 8.1 Obligatorisk `NenjimHub.conf` format 1
|
### 8.1 Obligatorisk `NenjimHub.conf` format 2
|
||||||
|
|
||||||
Hub'en læser kun `conf/NenjimHub.conf`. Filen skal eksistere som en læsbar regular file; en manglende eller ugyldig
|
Hub'en læser kun `conf/NenjimHub.conf`. Filen skal eksistere som en læsbar regular file; en manglende eller ugyldig
|
||||||
fil stopper startup, før Registry startes. `conf/NenjimHub.conf.example` er kun en versioneret copy-template: Hub'en
|
fil stopper startup, før Registry startes. `conf/NenjimHub.conf.example` er kun en versioneret copy-template: Hub'en
|
||||||
læser, kopierer og genererer den aldrig og falder hverken tilbage til den eller til en hardcoded procesliste.
|
læser, kopierer og genererer den aldrig og falder hverken tilbage til den eller til en hardcoded procesliste.
|
||||||
|
|
||||||
Den første faktiske entry skal være `FORMAT_VERSION=1`; før den må der kun stå blanke linjer og full-line
|
Den første faktiske entry skal være `FORMAT_VERSION=2`; før den må der kun stå blanke linjer og full-line
|
||||||
`#`-kommentarer. Den næste faktiske entry skal være den ene eksakte `[AUTO_START]`-header. Hver aktiv linje i
|
`#`-kommentarer. Den næste faktiske entry skal være den ene eksakte `[AUTO_START]`-header. Hver aktiv linje i
|
||||||
sektionen er ét `NenjimComponentId`, og rækkefølgen bevares præcist. Leading/trailing whitespace ignoreres, en trimmet
|
sektionen er ét `NenjimComponentId`, og rækkefølgen bevares præcist. Leading/trailing whitespace ignoreres, en trimmet
|
||||||
linje som begynder med `#` er en kommentar, og `#` efter faktisk indhold starter en trailing kommentar. Inde i
|
linje som begynder med `#` er en kommentar, og `#` efter faktisk indhold starter en trailing kommentar. Inde i
|
||||||
@@ -287,17 +277,19 @@ graph konstrueres mellem Registry- og Journal-start.
|
|||||||
indsætter eller omordner ikke dependencies. En krævet proces skal stå før sin consumer; eksempelvis skal
|
indsætter eller omordner ikke dependencies. En krævet proces skal stå før sin consumer; eksempelvis skal
|
||||||
`nenjim.javafx-runtime.service` stå før Mission Control og Process Manager. Mangler forgængeren, stopper konstruktion
|
`nenjim.javafx-runtime.service` stå før Mission Control og Process Manager. Mangler forgængeren, stopper konstruktion
|
||||||
eller startup fail-fast uden implicit reparation. En udeladt proces bliver hverken konstrueret, registreret, startet
|
eller startup fail-fast uden implicit reparation. En udeladt proces bliver hverken konstrueret, registreret, startet
|
||||||
eller vist af Process Manager. Den versionerede template vælger kun de obligatoriske Registry- og Journal-processer;
|
eller vist af Process Manager. Jupiter Perps Alarm kan vælges eksplicit, men er fortsat udeladt fra default-template.
|
||||||
alle øvrige processer skal tilføjes eksplicit til deploymentets private konfiguration.
|
|
||||||
|
|
||||||
Et minimalt headless eksempel, svarende til den versionerede template, er:
|
Et headless eksempel uden JavaFX og Test Evelyn er:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
[AUTO_START]
|
[AUTO_START]
|
||||||
nenjim.registry.service
|
nenjim.registry.service
|
||||||
nenjim.journal.service
|
nenjim.journal.service
|
||||||
|
assetaz.ticker.default
|
||||||
|
evelyn.service.prod
|
||||||
|
evelyn.iou-burner.prod
|
||||||
```
|
```
|
||||||
|
|
||||||
Tickerens egen component-konfiguration vælger fortsat eksplicit
|
Tickerens egen component-konfiguration vælger fortsat eksplicit
|
||||||
|
|||||||
+2
-4
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
||||||
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers, første version af det centrale
|
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers, første version af det centrale
|
||||||
> component Registry og obligatorisk Hub-konfiguration format 1 er implementeret. Resolver-, Context-,
|
> component Registry og obligatorisk Hub-konfiguration format 2 er implementeret. Resolver-, Context-,
|
||||||
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
||||||
> det er angivet.
|
> det er angivet.
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
| Passiv component | En `NenjimComponent`, hvis implementation ikke deltager i Hub-lifecycle gennem det canonical `NenjimProcess`. Den nuværende Hub konstruerer og registrerer hele den passive dependency graph efter Registry-start uafhængigt af `[AUTO_START]`, men starter den ikke. |
|
| Passiv component | En `NenjimComponent`, hvis implementation ikke deltager i Hub-lifecycle gennem det canonical `NenjimProcess`. Den nuværende Hub konstruerer og registrerer hele den passive dependency graph efter Registry-start uafhængigt af `[AUTO_START]`, men starter den ikke. |
|
||||||
| Valgt proces | Et kendt `NenjimProcess`-objekt, som Hub'en kun konstruerer, registrerer og starter, når dets ID forekommer i `[AUTO_START]`. Rollen som service eller applikation afgør ikke denne klassifikation. |
|
| Valgt proces | Et kendt `NenjimProcess`-objekt, som Hub'en kun konstruerer, registrerer og starter, når dets ID forekommer i `[AUTO_START]`. Rollen som service eller applikation afgør ikke denne klassifikation. |
|
||||||
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en validerer `conf/NenjimHub.conf` før proces-start, bootstrapper Registry, konstruerer den passive graph og konstruerer/registrerer/starter kun valgte processer i eksakt konfigurationsrækkefølge. Den tracker succesfulde start-ID'er, erklærer online point og ejer den afsluttende runtime wait, men er hverken component eller process og registreres ikke i sit eget Registry. |
|
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en validerer `conf/NenjimHub.conf` før proces-start, bootstrapper Registry, konstruerer den passive graph og konstruerer/registrerer/starter kun valgte processer i eksakt konfigurationsrækkefølge. Den tracker succesfulde start-ID'er, erklærer online point og ejer den afsluttende runtime wait, men er hverken component eller process og registreres ikke i sit eget Registry. |
|
||||||
| AUTO_START | Den obligatoriske, ordered sektion i `NenjimHub.conf` format 1. Den er deploymentets komplette manuelle proces-dependency closure; Registry og Journal skal være første og anden entry. Den udtrykker ikke Context resolution, discovery eller automatisk dependency construction. |
|
| AUTO_START | Den obligatoriske, ordered sektion i `NenjimHub.conf` format 2. Den er deploymentets komplette manuelle proces-dependency closure; Registry og Journal skal være første og anden entry. Den udtrykker ikke Context resolution, discovery eller automatisk dependency construction. |
|
||||||
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
||||||
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
||||||
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
||||||
@@ -46,8 +46,6 @@
|
|||||||
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
||||||
| 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. |
|
|
||||||
| 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. |
|
|
||||||
| 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. |
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -52,7 +52,7 @@ Registry retains its required exceptional order of start, self-registration, the
|
|||||||
|
|
||||||
### Keep deployment configuration separate from its template
|
### Keep deployment configuration separate from its template
|
||||||
|
|
||||||
The runtime path stays a private constant in the reference Hub. `conf/NenjimHub.conf.example` is version controlled and documented, but no code references that example path. The example deliberately contains only the mandatory Registry and Journal entries so it is safe and generic for downstream users; operators add their own deployment-specific processes and dependency order after copying it. The real file remains deployment-specific under the existing `*.conf` ignore rule. Documentation shows copying explicitly and includes the same minimal headless bootstrap.
|
The runtime path stays a private constant in the reference Hub. `conf/NenjimHub.conf.example` is version controlled and documented, but no code references that example path. The real file remains deployment-specific under the existing `*.conf` ignore rule. Documentation shows copying explicitly and includes a separate headless snippet.
|
||||||
|
|
||||||
## Risks / Trade-offs
|
## Risks / Trade-offs
|
||||||
|
|
||||||
@@ -64,7 +64,8 @@ The runtime path stays a private constant in the reference Hub. `conf/NenjimHub.
|
|||||||
|
|
||||||
## Migration Plan
|
## Migration Plan
|
||||||
|
|
||||||
1. Deploy the new build together with an operator-created `conf/NenjimHub.conf` copied from the version 1 example and extended with the deployment's own ordered process selection.
|
1. Deploy the new build together with an operator-created `conf/NenjimHub.conf` copied from the version 2 example and adjusted for the deployment role.
|
||||||
2. Validate that manually ordered dependencies precede their consumers; Registry and Journal must remain first and second.
|
2. Validate that manually ordered dependencies precede their consumers; Registry and Journal must remain first and second.
|
||||||
3. Start the Hub normally through the unchanged `Main` entry point.
|
3. Start the Hub normally through the unchanged `Main` entry point.
|
||||||
4. To roll back the software, restore the previous build and its prior deployment configuration expectations; the application never rewrites either runtime or example configuration.
|
4. To roll back the software, restore the previous build and its prior deployment configuration expectations; the application never rewrites either runtime or example configuration.
|
||||||
|
|
||||||
+4
-4
@@ -4,20 +4,20 @@
|
|||||||
|
|
||||||
## What Changes
|
## What Changes
|
||||||
|
|
||||||
- **BREAKING**: Require the Hub to read `conf/NenjimHub.conf` in strict format version 1 before any process starts; missing, unreadable, non-regular, malformed, or unsupported configuration fails startup without fallback.
|
- **BREAKING**: Require the Hub to read `conf/NenjimHub.conf` in strict format version 2 before any process starts; missing, unreadable, non-regular, malformed, or unsupported configuration fails startup without fallback.
|
||||||
- Add one mandatory ordered `[AUTO_START]` section whose active lines are canonical `NenjimComponentId` values, with defined whitespace, `#` comment, and blank-line termination rules.
|
- Add one mandatory ordered `[AUTO_START]` section whose active lines are canonical `NenjimComponentId` values, with defined whitespace, `#` comment, and blank-line termination rules.
|
||||||
- Validate the complete requested process sequence before Registry startup, including known process capability, uniqueness, and Registry/Journal placement as the first two entries.
|
- Validate the complete requested process sequence before Registry startup, including known process capability, uniqueness, and Registry/Journal placement as the first two entries.
|
||||||
- Bootstrap Registry, then eagerly construct and register the complete passive component graph, while lazily constructing, registering, and starting only the remaining configured process objects in exact configuration order.
|
- Bootstrap Registry, then eagerly construct and register the complete passive component graph, while lazily constructing, registering, and starting only the remaining configured process objects in exact configuration order.
|
||||||
- Preserve registration-before-start, post-success startup recording, fail-fast partial-startup semantics, the online point, and the blocking runtime wait without rollback or dependency inference.
|
- Preserve registration-before-start, post-success startup recording, fail-fast partial-startup semantics, the online point, and the blocking runtime wait without rollback or dependency inference.
|
||||||
- Make all modern Hub-known processes selectable, including the otherwise inactive Jupiter Perps Alarm, while an omitted process is neither constructed, registered, nor started.
|
- Make all modern Hub-known processes selectable, including the otherwise inactive Jupiter Perps Alarm, while an omitted process is neither constructed, registered, nor started.
|
||||||
- Add a documented, copy-only `conf/NenjimHub.conf.example` that provides a generic minimal bootstrap with only the mandatory Registry and Journal processes, plus headless configuration documentation.
|
- Add a documented `conf/NenjimHub.conf.example` that reproduces the former successful startup sequence and remains a copy-only template, plus headless configuration documentation.
|
||||||
- Remove the unconditional process-construction path and fixed startup list without adding public factory, discovery, lifecycle, Context, or configuration APIs.
|
- Remove the unconditional process-construction path and fixed startup list without adding public factory, discovery, lifecycle, Context, or configuration APIs.
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
### New Capabilities
|
### New Capabilities
|
||||||
|
|
||||||
- `nenjim-hub-configuration`: Defines the mandatory format 1 Hub configuration grammar, validation, ordered bootstrap and startup plan, lazy selected-process construction, omitted-process behavior, template role, and failure semantics.
|
- `nenjim-hub-configuration`: Defines the mandatory format 2 Hub configuration grammar, validation, ordered bootstrap and startup plan, lazy selected-process construction, omitted-process behavior, template role, and failure semantics.
|
||||||
|
|
||||||
### Modified Capabilities
|
### Modified Capabilities
|
||||||
|
|
||||||
@@ -29,5 +29,5 @@
|
|||||||
- Adds an implementation-only Hub configuration parser/model and process-construction catalogue under `com.r35157.nenjim.hubd.impl.ref` without changing the public `NenjimHub` API.
|
- Adds an implementation-only Hub configuration parser/model and process-construction catalogue under `com.r35157.nenjim.hubd.impl.ref` without changing the public `NenjimHub` API.
|
||||||
- Refactors `NenjimHubImpl` composition so Registry remains the bootstrap exception, passive dependencies remain eager, and process instances become lazy.
|
- Refactors `NenjimHubImpl` composition so Registry remains the bootstrap exception, passive dependencies remain eager, and process instances become lazy.
|
||||||
- Adds `conf/NenjimHub.conf.example` while retaining the ignored deployment-specific runtime file at `conf/NenjimHub.conf`.
|
- Adds `conf/NenjimHub.conf.example` while retaining the ignored deployment-specific runtime file at `conf/NenjimHub.conf`.
|
||||||
- Updates Nenjim architecture and terminology documentation to describe format 1, manual dependency ordering, a generic minimal template, headless operation, and actual successful-start snapshots.
|
- Updates Nenjim architecture and terminology documentation to describe format 2, manual dependency ordering, headless operation, and actual successful-start snapshots.
|
||||||
- Adds no automated tests, external dependencies, process rollback, automatic dependency resolution, dynamic discovery, or command-line configuration selection.
|
- Adds no automated tests, external dependencies, process rollback, automatic dependency resolution, dynamic discovery, or command-line configuration selection.
|
||||||
+12
-11
@@ -8,7 +8,7 @@ Defines the mandatory deployment configuration that selects which modern Nenjim
|
|||||||
The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all configuration parsing and validation before starting Registry or any other process. The path SHALL not be configurable through `Main`, command-line arguments, or the public `NenjimHub` API. A missing, unreadable, or non-regular runtime file SHALL fail Hub startup clearly and identify the configured path. The Hub SHALL not read, copy, generate, or fall back to `conf/NenjimHub.conf.example`, and SHALL not fall back to a hardcoded startup list.
|
The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all configuration parsing and validation before starting Registry or any other process. The path SHALL not be configurable through `Main`, command-line arguments, or the public `NenjimHub` API. A missing, unreadable, or non-regular runtime file SHALL fail Hub startup clearly and identify the configured path. The Hub SHALL not read, copy, generate, or fall back to `conf/NenjimHub.conf.example`, and SHALL not fall back to a hardcoded startup list.
|
||||||
|
|
||||||
#### Scenario: Runtime configuration is valid
|
#### Scenario: Runtime configuration is valid
|
||||||
- **WHEN** `conf/NenjimHub.conf` is a readable regular file containing a valid format 1 configuration
|
- **WHEN** `conf/NenjimHub.conf` is a readable regular file containing a valid format 2 configuration
|
||||||
- **THEN** the Hub derives its complete requested process order from that file before starting Registry
|
- **THEN** the Hub derives its complete requested process order from that file before starting Registry
|
||||||
|
|
||||||
#### Scenario: Runtime configuration cannot be read
|
#### Scenario: Runtime configuration cannot be read
|
||||||
@@ -19,13 +19,13 @@ The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all c
|
|||||||
- **WHEN** `conf/NenjimHub.conf` is absent but `conf/NenjimHub.conf.example` exists
|
- **WHEN** `conf/NenjimHub.conf` is absent but `conf/NenjimHub.conf.example` exists
|
||||||
- **THEN** startup fails without reading, copying, or using the example and without starting a hardcoded fallback set
|
- **THEN** startup fails without reading, copying, or using the example and without starting a hardcoded fallback set
|
||||||
|
|
||||||
### Requirement: Format 1 has one exact ordered AUTO_START section
|
### Requirement: Format 2 has one exact ordered AUTO_START section
|
||||||
The first actual configuration entry SHALL be exactly `FORMAT_VERSION=1`, after optional leading blank lines and full-line comments. Version 1 SHALL be the only supported version; a missing, malformed, duplicate, version 2, or otherwise unsupported declaration SHALL fail. The next actual entry SHALL be exactly one `[AUTO_START]` header, optionally followed only by a trailing `#` comment. Unknown, malformed, repeated, valued, or out-of-order sections and entries before `[AUTO_START]` SHALL fail.
|
The first actual configuration entry SHALL be exactly `FORMAT_VERSION=2`, after optional leading blank lines and full-line comments. Version 2 SHALL be the only supported version; a missing, malformed, duplicate, version 1, or otherwise unsupported declaration SHALL fail. The next actual entry SHALL be exactly one `[AUTO_START]` header, optionally followed only by a trailing `#` comment. Unknown, malformed, repeated, valued, or out-of-order sections and entries before `[AUTO_START]` SHALL fail.
|
||||||
|
|
||||||
Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line whose trimmed content begins with `#` SHALL be a full-line comment, and `#` after actual content SHALL begin a trailing comment. Inside `[AUTO_START]`, surrounding whitespace around a process ID SHALL be ignored; full-line and trailing comments SHALL be ignored; comment-only lines SHALL not terminate the section; and the first whitespace-only line or end of file SHALL terminate it. After termination, only blank lines and comments SHALL be accepted. Format 1 SHALL provide no quoting or escaping for `#`.
|
Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line whose trimmed content begins with `#` SHALL be a full-line comment, and `#` after actual content SHALL begin a trailing comment. Inside `[AUTO_START]`, surrounding whitespace around a process ID SHALL be ignored; full-line and trailing comments SHALL be ignored; comment-only lines SHALL not terminate the section; and the first whitespace-only line or end of file SHALL terminate it. After termination, only blank lines and comments SHALL be accepted. Format 2 SHALL provide no quoting or escaping for `#`.
|
||||||
|
|
||||||
#### Scenario: Leading comments and trailing comments are accepted
|
#### Scenario: Leading comments and trailing comments are accepted
|
||||||
- **WHEN** blank lines or full-line comments precede `FORMAT_VERSION=1`, and the version, section header, or process entries contain trailing `#` comments
|
- **WHEN** blank lines or full-line comments precede `FORMAT_VERSION=2`, and the version, section header, or process entries contain trailing `#` comments
|
||||||
- **THEN** comment text and permitted surrounding whitespace are ignored while the actual entries are parsed normally
|
- **THEN** comment text and permitted surrounding whitespace are ignored while the actual entries are parsed normally
|
||||||
|
|
||||||
#### Scenario: Comment inside AUTO_START does not terminate it
|
#### Scenario: Comment inside AUTO_START does not terminate it
|
||||||
@@ -41,7 +41,7 @@ Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line
|
|||||||
- **THEN** the section terminates successfully at end of file
|
- **THEN** the section terminates successfully at end of file
|
||||||
|
|
||||||
#### Scenario: Version declaration is invalid
|
#### Scenario: Version declaration is invalid
|
||||||
- **WHEN** the version is absent, malformed, duplicated, not the first actual entry, or has a value other than `1`
|
- **WHEN** the version is absent, malformed, duplicated, not the first actual entry, or has a value other than `2`
|
||||||
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line and reason
|
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line and reason
|
||||||
|
|
||||||
#### Scenario: Section structure is invalid
|
#### Scenario: Section structure is invalid
|
||||||
@@ -127,12 +127,13 @@ Registry and every selected process SHALL be registered before its `start()` cal
|
|||||||
- **THEN** the startup snapshot has the exact configured order, the online point is announced, and the Hub enters its existing blocking wait
|
- **THEN** the startup snapshot has the exact configured order, the online point is announced, and the Hub enters its existing blocking wait
|
||||||
|
|
||||||
### Requirement: The version-controlled example is a copy-only template
|
### Requirement: The version-controlled example is a copy-only template
|
||||||
The repository SHALL contain `conf/NenjimHub.conf.example` as a documented format 1 copy-only template with only the mandatory Registry and Journal entries active. It SHALL contain no optional deployment-specific process IDs. Its comments SHALL state that the file must be copied to `conf/NenjimHub.conf` before use, Registry and Journal are mandatory, order is preserved, and process dependencies must be listed manually before their consumers. Documentation SHALL show the same minimal bootstrap as a headless example and SHALL describe recursive dependency construction as future work rather than implemented behavior.
|
The repository SHALL contain `conf/NenjimHub.conf.example` as a documented format 2 template that reproduces the former successful process sequence without selecting `jupiter-perps-alarm.default`. Its comments SHALL state that Registry and Journal are mandatory, order is preserved, current process dependencies must be listed manually before consumers, JavaFX runtime precedes JavaFX applications, commenting out an entry prevents its process from being constructed or started, and the file must be copied to `conf/NenjimHub.conf` before use. Documentation SHALL include a headless example and SHALL describe recursive dependency construction as future work rather than implemented behavior.
|
||||||
|
|
||||||
#### Scenario: Example provides a generic minimal bootstrap
|
#### Scenario: Example reproduces the former startup sequence
|
||||||
- **WHEN** the example's uncommented process IDs are read in order
|
- **WHEN** the example's uncommented process IDs are read in order
|
||||||
- **THEN** they are only Registry and Journal, with no optional deployment-specific process ID present
|
- **THEN** they are Registry, Journal, Ticker, Production Evelyn, Test Evelyn, Production IOU Burner, JavaFX runtime, Mission Control, and Process Manager
|
||||||
|
|
||||||
#### Scenario: Operator uses the minimal headless example
|
#### Scenario: Operator uses a headless example
|
||||||
- **WHEN** the documented headless configuration is copied to the runtime path
|
- **WHEN** the documented headless configuration is copied to the runtime path
|
||||||
- **THEN** it selects only Registry and Journal and leaves every optional process for the operator to add explicitly
|
- **THEN** it selects Registry, Journal, Ticker, Production Evelyn, and Production IOU Burner without selecting Test Evelyn or any JavaFX process
|
||||||
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
## 1. Hub configuration
|
## 1. Hub configuration
|
||||||
|
|
||||||
- [x] 1.1 Add the package-private immutable Hub configuration model and strict format 1 parser for the fixed runtime path, including exact version/section/comment/termination grammar and path-and-line diagnostics.
|
- [x] 1.1 Add the package-private immutable Hub configuration model and strict format 2 parser for the fixed runtime path, including exact version/section/comment/termination grammar and path-and-line diagnostics.
|
||||||
- [x] 1.2 Validate canonical process IDs, duplicates, known process construction capability, and mandatory Registry/Journal positions before Registry startup.
|
- [x] 1.2 Validate canonical process IDs, duplicates, known process construction capability, and mandatory Registry/Journal positions before Registry startup.
|
||||||
|
|
||||||
## 2. Ordered runtime composition
|
## 2. Ordered runtime composition
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
## 3. Configuration template and documentation
|
## 3. Configuration template and documentation
|
||||||
|
|
||||||
- [x] 3.1 Add `conf/NenjimHub.conf.example` as a documented copy-only format 1 template containing only the mandatory Registry and Journal bootstrap entries, without deployment-specific optional processes.
|
- [x] 3.1 Add `conf/NenjimHub.conf.example` as a documented copy-only format 2 template reproducing the former startup sequence without enabling Jupiter Perps Alarm.
|
||||||
- [x] 3.2 Update Nenjim architecture, public documentation, and terminology for mandatory Hub configuration, exact grammar, manual dependency order, passive versus selected construction, omission, failure semantics, and headless operation.
|
- [x] 3.2 Update Nenjim architecture, public documentation, and terminology for mandatory Hub configuration, exact grammar, manual dependency order, passive versus selected construction, omission, failure semantics, and headless operation.
|
||||||
|
|
||||||
## 4. Verification
|
## 4. Verification
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
schema: spec-driven
|
|
||||||
created: 2026-09-03
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
## Context
|
|
||||||
|
|
||||||
The Ticker implementation already combines two roles on one object: `TickerService` supplies domain queries and `NenjimProcess` supplies Hub-managed startup. The service interface redundantly declares startup and also declares stop even though the canonical process API exposes no stop operation. Internally, Ticker must still stop sources that were started before a later source makes startup fail.
|
|
||||||
|
|
||||||
## Goals / Non-Goals
|
|
||||||
|
|
||||||
**Goals:**
|
|
||||||
|
|
||||||
- Make the public Ticker service view lifecycle-free.
|
|
||||||
- Preserve the identical implementation object as both `TickerService` and `NenjimProcess`.
|
|
||||||
- Preserve complete reverse-order cleanup of sources started during a failed startup attempt, including suppressed stop failures.
|
|
||||||
- Preserve Hub startup, source activation, persistence, callbacks, and latest-price semantics.
|
|
||||||
|
|
||||||
**Non-Goals:**
|
|
||||||
|
|
||||||
- No stop or restart addition to `NenjimProcess`.
|
|
||||||
- No Hub shutdown or process-control design.
|
|
||||||
- No change to `PriceSource.start(PriceSink)` or `PriceSource.stop()`.
|
|
||||||
- No cleanup of unrelated service interfaces and no new automated tests.
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
|
|
||||||
### Keep startup only on the process view
|
|
||||||
|
|
||||||
Delete both lifecycle declarations from `TickerService` and retain `TickerServiceImpl.start()` solely as its `NenjimProcess.start()` implementation. `NenjimHubImpl` already returns the constructed implementation as `NenjimProcess`, registers the same object, resolves that process view, and invokes startup; no Hub wiring change is needed.
|
|
||||||
|
|
||||||
### Keep source stopping as private failed-start rollback
|
|
||||||
|
|
||||||
Delete the public `TickerServiceImpl.stop()` method but retain its existing private rollback path. On startup failure the implementation enters its internal stopping state, drains accepted callbacks, stops every source marked started in reverse registration order, resets its transient state, and reports stop failures as suppressed exceptions on the startup failure. This preserves resource safety without offering ordinary shutdown control.
|
|
||||||
|
|
||||||
The internal stopped state remains available after rollback so the existing failed-start retry behavior is not changed. A successful start has no newly introduced stop or restart path.
|
|
||||||
|
|
||||||
## Risks / Trade-offs
|
|
||||||
|
|
||||||
- [A successful Ticker has no public shutdown path] → This is intentional until common Hub/process stop semantics are designed.
|
|
||||||
- [Private state names still describe stopping] → They model rollback of `PriceSource` resources, not an exposed Ticker lifecycle operation.
|
|
||||||
- [Downstream source callers may have compiled against the removed methods] → Treat the service API removal as the explicit breaking change and retain the unchanged process startup route for the Hub.
|
|
||||||
|
|
||||||
## Migration Plan
|
|
||||||
|
|
||||||
1. Recompile consumers against the lifecycle-free `TickerService` contract.
|
|
||||||
2. Remove direct service-level startup or stop calls; runtime startup remains configured and performed by the Hub.
|
|
||||||
3. Roll back the software version if a consumer cannot yet migrate; no data or configuration migration is required.
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
## Why
|
|
||||||
|
|
||||||
`TickerService` exposes `start()` and `stop()` to ordinary consumers even though the current Nenjim architecture makes the Hub the lifecycle owner and models active implementations separately through `NenjimProcess`. This leaks process control through a domain service API and promises a stop lifecycle that the canonical process contract does not support.
|
|
||||||
|
|
||||||
## What Changes
|
|
||||||
|
|
||||||
- **BREAKING**: Remove `start()` and `stop()` from the public `TickerService` contract.
|
|
||||||
- Keep `TickerServiceImpl` as both the lifecycle-free service view and the Hub-started `NenjimProcess` view.
|
|
||||||
- Remove the implementation's public general stop operation while retaining private rollback that stops every `PriceSource` started during a failed startup attempt.
|
|
||||||
- Keep `PriceSource` lifecycle contracts, latest-price behavior, persistence, Registry resolution, and Hub startup wiring unchanged.
|
|
||||||
- Update the canonical Ticker specification, JavaDoc, and Nenjim documentation to describe Hub-owned lifecycle.
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
### Modified Capabilities
|
|
||||||
|
|
||||||
- `assetaz-ticker-service`: Separates the ordinary latest-price service API from Hub-owned process startup and limits source stopping to failed-start cleanup.
|
|
||||||
|
|
||||||
## Impact
|
|
||||||
|
|
||||||
- Changes the public `com.r35157.assetaz.services.ticker.TickerService` source contract and the public methods of `TickerServiceImpl`.
|
|
||||||
- Existing callers must stop invoking Ticker lifecycle through the service view; the Hub continues to invoke startup through `NenjimProcess`.
|
|
||||||
- Adds no common stop/restart support, shutdown orchestration, source lifecycle changes, dependencies, or automated tests.
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
## MODIFIED Requirements
|
|
||||||
|
|
||||||
### Requirement: Price sources are discovered and lifecycle-managed
|
|
||||||
Nenjim SHALL be able to construct and register each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL receive the public Nenjim Registry through constructor injection and resolve its explicitly configured `PriceSource` component IDs, while ordinary ticker clients SHALL receive only the latest-price domain API and SHALL NOT receive public source-registration, start, stop, restart, or other lifecycle operations. The same reference implementation object SHALL implement both `TickerService` and `NenjimProcess`; the Hub SHALL start it only through its `NenjimProcess` view. The ticker SHALL fail clearly when a configured ID is missing or names a component that does not implement `PriceSource`; it SHALL NOT silently select the first source listed for the interface. The ticker SHALL identify each resolved source by its `TradingPair` and stable source name, reject duplicate identities clearly, and start each active source by passing itself as `PriceSink`. If startup fails, the ticker SHALL stop every source already started during that attempt before reporting the failure, but SHALL expose no general Ticker stop operation. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
|
|
||||||
|
|
||||||
#### Scenario: Sources are constructed independently
|
|
||||||
- **WHEN** the NenjimHub constructs and registers a price source before constructing or starting the ticker
|
|
||||||
- **THEN** source construction requires neither a `PriceSink` nor a ticker reference and leaves the source unstarted
|
|
||||||
|
|
||||||
#### Scenario: Ticker resolves configured source components
|
|
||||||
- **WHEN** the ticker initializes its source set from explicit component IDs and an injected public Registry
|
|
||||||
- **THEN** it resolves each ID as `PriceSource`, not `PriceSink`, and manages only the resolved configured sources internally
|
|
||||||
|
|
||||||
#### Scenario: Missing configured source fails clearly
|
|
||||||
- **WHEN** the ticker is configured with an absent component ID or an ID whose object is not a `PriceSource`
|
|
||||||
- **THEN** construction fails with a diagnostic identifying the ID and required interface
|
|
||||||
|
|
||||||
#### Scenario: Ordinary clients cannot manage sources
|
|
||||||
- **WHEN** a caller uses the public `TickerService` contract
|
|
||||||
- **THEN** the contract exposes only latest-price queries and no source-registration, start, stop, restart, or other lifecycle operation
|
|
||||||
|
|
||||||
#### Scenario: Hub starts Ticker through its process view
|
|
||||||
- **WHEN** the configured Ticker process is reached in the Hub startup plan
|
|
||||||
- **THEN** the same object registered as `TickerService` and `NenjimProcess` is started through `NenjimProcess`
|
|
||||||
|
|
||||||
#### Scenario: Distinct configured sources are accepted
|
|
||||||
- **WHEN** the ticker resolves two configured sources with different source names or trading pairs
|
|
||||||
- **THEN** it accepts both and manages each source independently
|
|
||||||
|
|
||||||
#### Scenario: Duplicate persistent identity is rejected
|
|
||||||
- **WHEN** the ticker resolves sources with the same trading pair and source name
|
|
||||||
- **THEN** initialization fails with an error identifying the duplicate source history
|
|
||||||
|
|
||||||
#### Scenario: Unsafe source name is rejected
|
|
||||||
- **WHEN** a source name is empty or contains a slash, backslash, `..`, or a control character
|
|
||||||
- **THEN** ticker initialization or startup fails clearly without rewriting the source name or creating filesystem content
|
|
||||||
|
|
||||||
#### Scenario: Active source lifecycle is managed
|
|
||||||
- **WHEN** one active source starts successfully and a later active source makes Ticker startup fail
|
|
||||||
- **THEN** the ticker stops every source started during that attempt and reports the startup failure without exposing a general stop operation
|
|
||||||
|
|
||||||
#### Scenario: Repeated source start preserves existing sink
|
|
||||||
- **WHEN** an already-started source receives another start call with a different sink
|
|
||||||
- **THEN** it rejects the call before replacing the sink from the successful start
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
## 1. Ticker API and implementation
|
|
||||||
|
|
||||||
- [x] 1.1 Remove `start()` and `stop()` from `TickerService` and document the lifecycle-free consumer contract.
|
|
||||||
- [x] 1.2 Keep `TickerServiceImpl.start()` as `NenjimProcess.start()`, remove its public normal stop operation, and preserve private failed-start source cleanup.
|
|
||||||
- [x] 1.3 Confirm Hub construction, Registry resolution, process startup, and latest-price behavior remain unchanged.
|
|
||||||
|
|
||||||
## 2. Specification and documentation
|
|
||||||
|
|
||||||
- [x] 2.1 Update the canonical `assetaz-ticker-service` capability to distinguish service queries, Hub-owned startup, and failed-start rollback.
|
|
||||||
- [x] 2.2 Update Nenjim architecture, public, terminology, and relevant JavaDoc descriptions consistently.
|
|
||||||
|
|
||||||
## 3. Verification
|
|
||||||
|
|
||||||
- [x] 3.1 Run strict OpenSpec validation and archive the completed change into the canonical specifications.
|
|
||||||
- [x] 3.2 Run the complete Gradle build and inspect the final diff for lifecycle leaks or unintended behavior changes.
|
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
schema: spec-driven
|
|
||||||
created: 2026-09-04
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
## 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
@@ -1,23 +0,0 @@
|
|||||||
## 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
@@ -1,59 +0,0 @@
|
|||||||
## 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
@@ -1,15 +0,0 @@
|
|||||||
## 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
@@ -1,2 +0,0 @@
|
|||||||
schema: spec-driven
|
|
||||||
created: 2026-09-04
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
## 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
@@ -1,25 +0,0 @@
|
|||||||
## 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
@@ -1,78 +0,0 @@
|
|||||||
## 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
@@ -1,13 +0,0 @@
|
|||||||
## 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.
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
Provide AssetAZ callers with typed latest prices backed by explicitly enabled, durable observation histories.
|
Provide AssetAZ callers with typed latest prices backed by explicitly enabled, durable observation histories.
|
||||||
## Requirements
|
## Requirements
|
||||||
### Requirement: Price sources are discovered and lifecycle-managed
|
### Requirement: Price sources are discovered and lifecycle-managed
|
||||||
Nenjim SHALL be able to construct and register each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL receive the public Nenjim Registry through constructor injection and resolve its explicitly configured `PriceSource` component IDs, while ordinary ticker clients SHALL receive only the latest-price domain API and SHALL NOT receive public source-registration, start, stop, restart, or other lifecycle operations. The same reference implementation object SHALL implement both `TickerService` and `NenjimProcess`; the Hub SHALL start it only through its `NenjimProcess` view. The ticker SHALL fail clearly when a configured ID is missing or names a component that does not implement `PriceSource`; it SHALL NOT silently select the first source listed for the interface. The ticker SHALL identify each resolved source by its `TradingPair` and stable source name, reject duplicate identities clearly, and start each active source by passing itself as `PriceSink`. If startup fails, the ticker SHALL stop every source already started during that attempt before reporting the failure, but SHALL expose no general Ticker stop operation. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
|
Nenjim SHALL be able to construct and register each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL receive the public Nenjim Registry through constructor injection and resolve its explicitly configured `PriceSource` component IDs, while ordinary ticker clients SHALL NOT receive a public operation for adding or managing sources. The ticker SHALL fail clearly when a configured ID is missing or names a component that does not implement `PriceSource`; it SHALL NOT silently select the first source listed for the interface. The ticker SHALL identify each resolved source by its `TradingPair` and stable source name, reject duplicate identities clearly, start each active source by passing itself as `PriceSink`, and stop every source it started. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
|
||||||
|
|
||||||
#### Scenario: Sources are constructed independently
|
#### Scenario: Sources are constructed independently
|
||||||
- **WHEN** the NenjimHub constructs and registers a price source before constructing or starting the ticker
|
- **WHEN** the NenjimHub constructs and registers a price source before constructing or starting the ticker
|
||||||
@@ -21,11 +21,7 @@ Nenjim SHALL be able to construct and register each price source independently i
|
|||||||
|
|
||||||
#### Scenario: Ordinary clients cannot manage sources
|
#### Scenario: Ordinary clients cannot manage sources
|
||||||
- **WHEN** a caller uses the public `TickerService` contract
|
- **WHEN** a caller uses the public `TickerService` contract
|
||||||
- **THEN** the contract exposes only latest-price queries and no source-registration, start, stop, restart, or other lifecycle operation
|
- **THEN** the contract exposes ticker lifecycle and latest-price queries but no source-registration operation
|
||||||
|
|
||||||
#### Scenario: Hub starts Ticker through its process view
|
|
||||||
- **WHEN** the configured Ticker process is reached in the Hub startup plan
|
|
||||||
- **THEN** the same object registered as `TickerService` and `NenjimProcess` is started through `NenjimProcess`
|
|
||||||
|
|
||||||
#### Scenario: Distinct configured sources are accepted
|
#### Scenario: Distinct configured sources are accepted
|
||||||
- **WHEN** the ticker resolves two configured sources with different source names or trading pairs
|
- **WHEN** the ticker resolves two configured sources with different source names or trading pairs
|
||||||
@@ -40,8 +36,8 @@ Nenjim SHALL be able to construct and register each price source independently i
|
|||||||
- **THEN** ticker initialization or startup fails clearly without rewriting the source name or creating filesystem content
|
- **THEN** ticker initialization or startup fails clearly without rewriting the source name or creating filesystem content
|
||||||
|
|
||||||
#### Scenario: Active source lifecycle is managed
|
#### Scenario: Active source lifecycle is managed
|
||||||
- **WHEN** one active source starts successfully and a later active source makes Ticker startup fail
|
- **WHEN** the ticker starts and later stops with an active Registry-resolved source
|
||||||
- **THEN** the ticker stops every source started during that attempt and reports the startup failure without exposing a general stop operation
|
- **THEN** it calls `start` with itself as `PriceSink` and subsequently stops the source without leaving source-owned resources running
|
||||||
|
|
||||||
#### Scenario: Repeated source start preserves existing sink
|
#### Scenario: Repeated source start preserves existing sink
|
||||||
- **WHEN** an already-started source receives another start call with a different sink
|
- **WHEN** an already-started source receives another start call with a different sink
|
||||||
|
|||||||
@@ -3,17 +3,19 @@
|
|||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Defines the Evelyn-specific service that safely converts prioritized wallet assets into Evelyn IOU, burns the complete visible EVE balance, and announces only finalized burns without automatically repeating transactions whose outcomes may be unknown.
|
Defines the Evelyn-specific service that safely converts prioritized wallet assets into Evelyn IOU, burns the complete visible EVE balance, and announces only finalized burns without automatically repeating transactions whose outcomes may be unknown.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Requirement: Evelyn-specific public service boundary
|
### 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.
|
The system SHALL expose `EvelynIOUBurnerService` under `com.fanitas.evelyn.service.burner` with only `start()` and `stop()` operations, and SHALL keep its reference implementation under `com.fanitas.evelyn.service.burner.impl.ref`. 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
|
#### Scenario: Downstream lifecycle consumer
|
||||||
- **WHEN** a downstream component depends on the Evelyn burner API
|
- **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
|
- **THEN** it can start and stop the service without depending on reference-implementation types or a generic burner model
|
||||||
|
|
||||||
#### Scenario: Input model remains domain-level
|
#### Scenario: Input model remains domain-level
|
||||||
- **WHEN** the Hub configures accepted buyback currencies while constructing the process implementation
|
- **WHEN** callers configure accepted buyback currencies
|
||||||
- **THEN** it uses existing `MoneyAmount` values rather than burner-specific Solana, SPL-token, or input-asset types
|
- **THEN** they use existing `MoneyAmount` values rather than burner-specific Solana, SPL-token, or input-asset types
|
||||||
|
|
||||||
### Requirement: Ordered AssetAZ input configuration
|
### 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 pricing and processing. An empty list SHALL be valid and SHALL still permit directly deposited EVE to be burned.
|
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 pricing and processing. An empty list SHALL be valid and SHALL still permit directly deposited EVE to be burned.
|
||||||
@@ -63,21 +65,21 @@ The service SHALL fix the EVE mint as `meveYG2iXYSkgSUn1T1uxcthH1EGMZdRHGgCntXZA
|
|||||||
- **THEN** it uses the fixed EVE mint and the canonical EVE/USDT pair without accepting replacements through construction
|
- **THEN** it uses the fixed EVE mint and the canonical EVE/USDT pair without accepting replacements through construction
|
||||||
|
|
||||||
### Requirement: Immediate sequential fixed-delay lifecycle
|
### 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.
|
Each service instance SHALL own at most one dedicated background worker. A successful `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. `stop()` SHALL prevent new iterations, interrupt and stop the worker cleanly, and preserve interruption if the stopping thread is interrupted. A repeated start while active SHALL NOT create another worker.
|
||||||
|
|
||||||
Pending burn information and swap/burn suspension flags SHALL be process-local only. A complete process restart SHALL clear those states. The public JavaDoc SHALL disclose that restart loses reconciliation state and can weaken duplicate prevention until unknown transactions have been reconciled externally. Neither the service interface nor the implementation SHALL expose ordinary stop or restart operations.
|
Pending burn information and swap/burn suspension flags SHALL be process-local only. A completed explicit stop followed by start SHALL clear those states. The public JavaDoc SHALL disclose that this reset, or a process restart, loses reconciliation state and can weaken duplicate-prevention until unknown transactions have been reconciled externally.
|
||||||
|
|
||||||
#### Scenario: First and later iterations
|
#### Scenario: First and later iterations
|
||||||
- **WHEN** the Hub starts the Burner process and one iteration takes longer than usual
|
- **WHEN** the service starts 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
|
- **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
|
#### Scenario: Active service is started again
|
||||||
- **WHEN** `NenjimProcess.start()` is called while the instance already owns an active worker
|
- **WHEN** `start()` is called while the instance already owns an active worker
|
||||||
- **THEN** the call is rejected without creating an overlapping worker
|
- **THEN** the call is rejected without creating an overlapping worker
|
||||||
|
|
||||||
#### Scenario: Explicit lifecycle reset
|
#### Scenario: Explicit lifecycle reset
|
||||||
- **WHEN** a complete process restart occurs after pending or suspended process-local state has been externally reconciled
|
- **WHEN** a service with pending or suspended process-local state is cleanly stopped and then explicitly started
|
||||||
- **THEN** the new process begins without that prior in-memory state and reloads wallet and chain state normally
|
- **THEN** the new lifecycle begins without that state and reloads wallet and chain state normally
|
||||||
|
|
||||||
### Requirement: Pending burn is handled before all new work
|
### Requirement: Pending burn is handled before all new work
|
||||||
At the start of an iteration, if a prior burn signature is pending, the service SHALL only await that exact signature at `FINALIZED`. It SHALL submit no swap or burn in that iteration. An unknown timeout or I/O outcome SHALL retain the pending state and end the iteration; definitive success SHALL make the one associated notification attempt and end the iteration; and definitive on-chain failure SHALL log the slot and complete failure details, clear pending state, send no notification, and defer any new wallet decision to a later iteration.
|
At the start of an iteration, if a prior burn signature is pending, the service SHALL only await that exact signature at `FINALIZED`. It SHALL submit no swap or burn in that iteration. An unknown timeout or I/O outcome SHALL retain the pending state and end the iteration; definitive success SHALL make the one associated notification attempt and end the iteration; and definitive on-chain failure SHALL log the slot and complete failure details, clear pending state, send no notification, and defer any new wallet decision to a later iteration.
|
||||||
@@ -142,7 +144,7 @@ The SOL reserve comparison SHALL use the native balance without estimating or su
|
|||||||
### Requirement: At most one confirmed swap attempt per iteration
|
### 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.
|
After selecting a positive exact amount, the service SHALL invoke the existing Jupiter exact-input operation once with the selected CIS-resolved input mint, fixed EVE output mint, selected amount, and configured slippage. Normal return SHALL be treated as already `CONFIRMED` and SHALL NOT trigger a second confirmation wait. Once invoked, no later input currency SHALL be attempted in that iteration regardless of success or failure, and the service SHALL proceed to reload the complete EVE balance whenever processing was not interrupted.
|
||||||
|
|
||||||
A structured definitive on-chain swap failure SHALL be logged without suspending later-process swaps. A timed-out structured outcome SHALL suspend all later swaps in that process. Because the Jupiter `IOException` and interruption contracts can represent an unknown post-submission result, either SHALL conservatively suspend later swaps. The service SHALL never automatically rebuild, re-sign, retry, or resubmit such a swap; while swaps are suspended, later iterations SHALL continue only their burn path. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation.
|
A structured definitive on-chain swap failure SHALL be logged without suspending later-lifecycle swaps. A timed-out structured outcome SHALL suspend all later swaps in that lifecycle. 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.
|
||||||
|
|
||||||
#### Scenario: First swap fails definitively
|
#### Scenario: First swap fails definitively
|
||||||
- **WHEN** the first attempted input returns a definitive on-chain failure
|
- **WHEN** the first attempted input returns a definitive on-chain failure
|
||||||
@@ -150,7 +152,7 @@ A structured definitive on-chain swap failure SHALL be logged without suspending
|
|||||||
|
|
||||||
#### Scenario: Swap outcome is unknown
|
#### 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
|
- **WHEN** the attempted swap times out, throws an I/O failure that permits unknown post-submission status, or is interrupted
|
||||||
- **THEN** no equivalent or alternative swap is attempted and all later swaps remain suspended until external reconciliation and a complete process restart
|
- **THEN** no equivalent or alternative swap is attempted and all later swaps remain suspended until explicit stop/start
|
||||||
|
|
||||||
#### Scenario: Swap returns normally
|
#### Scenario: Swap returns normally
|
||||||
- **WHEN** Jupiter returns a successful result whose transaction already reached `CONFIRMED`
|
- **WHEN** Jupiter returns a successful result whose transaction already reached `CONFIRMED`
|
||||||
@@ -181,7 +183,7 @@ Immediately before a new burn, the service SHALL obtain the latest canonical EVE
|
|||||||
### Requirement: Single complete-balance burn submission and pending state
|
### 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.
|
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.
|
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 lifecycle. It MAY continue ordinary iteration work that does not submit another burn. Explicit stop/start SHALL reset that suspension.
|
||||||
|
|
||||||
#### Scenario: Burn returns a signature
|
#### Scenario: Burn returns a signature
|
||||||
- **WHEN** the wallet submits the complete observed EVE balance and returns a non-blank signature
|
- **WHEN** the wallet submits the complete observed EVE balance and returns a non-blank signature
|
||||||
@@ -189,7 +191,7 @@ If burn submission fails without a usable signature and its outcome may be unkno
|
|||||||
|
|
||||||
#### Scenario: Submission outcome may be unknown without a signature
|
#### 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
|
- **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
|
- **THEN** the service performs no further burn submission until explicit stop/start
|
||||||
|
|
||||||
### Requirement: Finalized burn outcome governs completion
|
### Requirement: Finalized burn outcome governs completion
|
||||||
Every known burn signature SHALL be awaited through the Solana blockchain at `FINALIZED` with exactly the configured finalization timeout. `SUCCEEDED` SHALL authorize notification; `FAILED` SHALL log the slot and complete on-chain failure details, clear pending state, and send no notification; `TIMED_OUT` and `IOException` SHALL keep the pending signature because its result remains unknown; and `InterruptedException` SHALL preserve interruption and pending state. None of these outcomes SHALL cause automatic transaction resubmission.
|
Every known burn signature SHALL be awaited through the Solana blockchain at `FINALIZED` with exactly the configured finalization timeout. `SUCCEEDED` SHALL authorize notification; `FAILED` SHALL log the slot and complete on-chain failure details, clear pending state, and send no notification; `TIMED_OUT` and `IOException` SHALL keep the pending signature because its result remains unknown; and `InterruptedException` SHALL preserve interruption and pending state. None of these outcomes SHALL cause automatic transaction resubmission.
|
||||||
|
|||||||
@@ -72,16 +72,9 @@ The implementation's `start()` SHALL be the only operation that calls `Platform.
|
|||||||
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
||||||
|
|
||||||
### Requirement: Deferred JavaFX runtime features remain absent
|
### Requirement: Deferred JavaFX runtime features remain absent
|
||||||
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. Hub configuration MAY select the JavaFX runtime and applications only through explicit known process IDs in dependency-safe order; it SHALL not add configuration to the JavaFX service API. A valid headless Hub configuration MAY omit the runtime and every JavaFX application, in which case the Hub SHALL not construct or initialize the JavaFX runtime.
|
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. This change SHALL not add `NenjimHub.conf` or configurable autorun.
|
||||||
|
|
||||||
#### Scenario: Configured JavaFX consumers follow runtime startup
|
|
||||||
- **WHEN** a valid Hub configuration selects JavaFX applications
|
|
||||||
- **THEN** it lists `nenjim.javafx-runtime.service` before those applications and the shared runtime is started before they submit UI work
|
|
||||||
|
|
||||||
#### Scenario: Headless configuration omits JavaFX
|
|
||||||
- **WHEN** a valid Hub configuration omits the JavaFX runtime and all JavaFX applications
|
|
||||||
- **THEN** no JavaFX runtime or application process is constructed and the JavaFX toolkit remains uninitialized by Hub startup
|
|
||||||
|
|
||||||
#### Scenario: Runtime boundary is inspected
|
#### Scenario: Runtime boundary is inspected
|
||||||
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
||||||
- **THEN** only explicit one-time runtime startup, asynchronous task submission, configuration-ordered dependency injection, and optional omission are present
|
- **THEN** only explicit one-time startup, asynchronous task submission, fixed Hub ordering, and dependency injection are present
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ A non-bootstrap application or service that owns a component choice SHALL receiv
|
|||||||
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
||||||
|
|
||||||
### Requirement: Main is a thin outer bootstrap
|
### Requirement: Main is a thin outer bootstrap
|
||||||
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not select a configuration path or construct the Registry, manager, or business graph.
|
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not construct the Registry, manager, or business graph.
|
||||||
|
|
||||||
#### Scenario: Start through the outer bootstrap
|
#### Scenario: Start through the outer bootstrap
|
||||||
- **WHEN** the configured Java entry point is invoked
|
- **WHEN** the configured Java entry point is invoked
|
||||||
@@ -116,11 +116,11 @@ A non-bootstrap application or service that owns a component choice SHALL receiv
|
|||||||
|
|
||||||
#### Scenario: No duplicate composition path remains
|
#### Scenario: No duplicate composition path remains
|
||||||
- **WHEN** the source tree is inspected after migration
|
- **WHEN** the source tree is inspected after migration
|
||||||
- **THEN** the former Registry-manager and hardcoded autorun paths are absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of configuration-directed composition
|
- **THEN** the former Registry-manager composition path is absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of the current hardcoded component graph
|
||||||
|
|
||||||
#### Scenario: Bootstrap has no composition responsibility
|
#### Scenario: Bootstrap has no composition responsibility
|
||||||
- **WHEN** the outer bootstrap source is inspected
|
- **WHEN** the outer bootstrap source is inspected
|
||||||
- **THEN** configuration parsing, composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
- **THEN** composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
||||||
|
|
||||||
### Requirement: Registry terminology and package boundaries are consistent
|
### Requirement: Registry terminology and package boundaries are consistent
|
||||||
Public component contracts SHALL live in `com.r35157.nenjim.component`; the canonical process contract SHALL live in `com.r35157.nenjim.process`; the global Hub contract SHALL live in `com.r35157.nenjim.hubd`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; and the component ID SHALL live in its `.valuetypes` package. Registry storage and its package-private administration view SHALL live in `.impl.ref`, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use `.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
Public component contracts SHALL live in `com.r35157.nenjim.component`; the canonical process contract SHALL live in `com.r35157.nenjim.process`; the global Hub contract SHALL live in `com.r35157.nenjim.hubd`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; and the component ID SHALL live in its `.valuetypes` package. Registry storage and its package-private administration view SHALL live in `.impl.ref`, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use `.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
||||||
@@ -138,11 +138,11 @@ Public component contracts SHALL live in `com.r35157.nenjim.component`; the cano
|
|||||||
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
|
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
|
||||||
|
|
||||||
### Requirement: Deferred runtime features remain absent
|
### Requirement: Deferred runtime features remain absent
|
||||||
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. Hub autorun configuration SHALL remain a private startup input that selects only the implementation's known process constructors in explicit manual order. Except for that selection and the Process Manager's read-only polling of the existing started-process snapshot, the Hub SHALL not add lifecycle events, subscriptions, process controls, stop, restart, upgrade, health monitoring, recovery, automatic dependency construction, or context-bound dependency access.
|
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. Except for the Process Manager's read-only polling of the existing started-process snapshot, the Hub SHALL not add configurable autorun, lifecycle events, subscriptions, process controls, stop, restart, upgrade, health monitoring, recovery, or context-bound dependency access.
|
||||||
|
|
||||||
#### Scenario: Consumer inspects the first Registry API
|
#### Scenario: Consumer inspects the first Registry API
|
||||||
- **WHEN** a consumer examines the public Registry, Registry-manager, process, Hub, and Process Manager contracts
|
- **WHEN** a consumer examines the public Registry, Registry-manager, process, Hub, and Process Manager contracts
|
||||||
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, Hub startup/snapshots, and the Process Manager role are available and configuration, discovery, resolution, and control remain absent from public APIs
|
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, Hub startup/snapshots, and the Process Manager role are available and no deferred runtime feature is exposed
|
||||||
|
|
||||||
### Requirement: Components and processes have minimal orthogonal contracts
|
### Requirement: Components and processes have minimal orthogonal contracts
|
||||||
`NenjimComponent` SHALL remain a pure marker with no component-ID or lifecycle property. The one canonical `com.r35157.nenjim.process.NenjimProcess` SHALL extend `NenjimComponent` and SHALL expose only `start() throws Exception` as its common lifecycle operation. Service and application SHALL remain independent domain or orchestration roles: an implementation in either role SHALL additionally implement `NenjimProcess` only when it has a Hub-managed active startup lifecycle. Passive components SHALL not implement `NenjimProcess`, and no `NenjimApplication` compatibility type SHALL remain.
|
`NenjimComponent` SHALL remain a pure marker with no component-ID or lifecycle property. The one canonical `com.r35157.nenjim.process.NenjimProcess` SHALL extend `NenjimComponent` and SHALL expose only `start() throws Exception` as its common lifecycle operation. Service and application SHALL remain independent domain or orchestration roles: an implementation in either role SHALL additionally implement `NenjimProcess` only when it has a Hub-managed active startup lifecycle. Passive components SHALL not implement `NenjimProcess`, and no `NenjimApplication` compatibility type SHALL remain.
|
||||||
@@ -186,63 +186,63 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
|||||||
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
||||||
|
|
||||||
### Requirement: NenjimHub owns composition and runtime startup
|
### 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 complete passive graph, then lazily construct, register, start, and record only the remaining configured processes in exact order beginning with Journal. 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.
|
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the Journal subsystem as `nenjim.journal.service`, start Journal second, and continue to the complete hardcoded business component set from issue #76 only after Journal startup succeeds. During application composition the Hub SHALL construct exactly one `JavaFxRuntimeServiceImpl`, register it as `nenjim.javafx-runtime.service`, inject its service view into Mission Control and Process Manager, construct the Process Manager with itself, and register that manager as `nenjim.process-manager.default`. The Hub SHALL resolve only explicitly enabled IDs through `NenjimProcess`, start the JavaFX runtime service after enabled non-JavaFX business processes and before Mission Control and Process Manager, start Process Manager last, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup, and the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
||||||
|
|
||||||
#### Scenario: Registry starts before passive graph construction
|
#### Scenario: Registry starts before graph construction
|
||||||
- **WHEN** the Hub has validated a complete startup plan and performs its first startup attempt
|
- **WHEN** the Hub 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
|
- **THEN** it starts, registers, and records Registry first before constructing Journal or components in the business graph
|
||||||
|
|
||||||
#### Scenario: Journal follows passive composition but starts second
|
#### Scenario: Journal starts before business composition
|
||||||
- **WHEN** Registry bootstrap and passive graph construction have succeeded
|
- **WHEN** Registry startup and self-registration have succeeded
|
||||||
- **THEN** the Hub constructs, registers, starts, and records Journal before any later configured process, so Journal remains the second successful process
|
- **THEN** the Hub registers, starts, and records Journal before constructing components in the business graph
|
||||||
|
|
||||||
#### Scenario: Registry is process-discoverable
|
#### Scenario: Registry is process-discoverable
|
||||||
- **WHEN** the Registry registration has completed
|
- **WHEN** the Registry registration has completed
|
||||||
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||||
|
|
||||||
#### Scenario: Journal is process-discoverable
|
#### Scenario: Journal is process-discoverable
|
||||||
- **WHEN** the configured Journal construction and registration have completed
|
- **WHEN** the Journal registration has completed
|
||||||
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
- **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
|
#### Scenario: JavaFX runtime is process-discoverable
|
||||||
- **WHEN** `nenjim.javafx-runtime.service` is selected and its registration has completed
|
- **WHEN** Hub-owned application composition has completed
|
||||||
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under that ID
|
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under `nenjim.javafx-runtime.service`
|
||||||
|
|
||||||
#### Scenario: Selected JavaFX applications share the runtime
|
#### Scenario: JavaFX applications share the runtime
|
||||||
- **WHEN** JavaFX runtime precedes selected Mission Control or Process Manager entries
|
- **WHEN** Hub-owned application composition has completed
|
||||||
- **THEN** each selected application retains the same already-constructed `JavaFxRuntimeService` instance registered by the Hub
|
- **THEN** Mission Control and Process Manager retain the same `JavaFxRuntimeService` instance registered by the Hub
|
||||||
|
|
||||||
#### Scenario: Selected Process Manager is process-discoverable
|
#### Scenario: Process Manager is process-discoverable
|
||||||
- **WHEN** `nenjim.process-manager.default` is selected after JavaFX runtime and its construction and registration complete
|
- **WHEN** Hub-owned application composition has completed
|
||||||
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess`
|
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess` under `nenjim.process-manager.default`
|
||||||
|
|
||||||
#### Scenario: Required passive bindings are registered
|
#### Scenario: Required hardcoded bindings are registered
|
||||||
- **WHEN** Hub passive composition completes
|
- **WHEN** Hub 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
|
- **THEN** Journal, the JavaFX runtime, the Process Manager, and all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding both service managers
|
||||||
|
|
||||||
#### Scenario: Configured startup subset preserves order
|
#### Scenario: Explicit startup subset preserves order
|
||||||
- **WHEN** Hub startup completes successfully
|
- **WHEN** Hub startup succeeds through the Process Manager phase
|
||||||
- **THEN** successful process IDs exactly equal the validated `[AUTO_START]` entries in their declared order
|
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
|
||||||
#### Scenario: Omitted processes do not enter the Registry
|
#### Scenario: Deliberately inactive processes remain inactive
|
||||||
- **WHEN** a modern process ID is omitted from `[AUTO_START]`
|
- **WHEN** Hub startup reaches its online point
|
||||||
- **THEN** that process is neither constructed, registered, started, nor recorded even though its implementation is known to the Hub
|
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
|
||||||
|
|
||||||
#### Scenario: Journal failure is fail-fast
|
#### Scenario: Journal failure is fail-fast
|
||||||
- **WHEN** Journal construction, registration, or process startup throws
|
- **WHEN** Journal 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
|
- **THEN** Registry remains the only recorded process, no business component 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
|
#### Scenario: JavaFX runtime failure is fail-fast
|
||||||
- **WHEN** selected JavaFX runtime construction, registration, or startup throws
|
- **WHEN** JavaFX runtime service 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
|
- **THEN** its ID is not recorded, Mission Control and Process Manager do not start, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
#### Scenario: Hub start is not retried
|
#### Scenario: Hub start is not retried
|
||||||
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
- **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
|
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||||
|
|
||||||
#### Scenario: Startup failure is fail-fast
|
#### Scenario: Startup failure is fail-fast
|
||||||
- **WHEN** any passive composition, selected construction, registration, or process startup operation throws
|
- **WHEN** any process startup 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
|
- **THEN** the exception propagates, no later process starts, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||||
|
|
||||||
### Requirement: Started-process snapshots are ordered immutable and truthful
|
### Requirement: Started-process snapshots are ordered immutable and truthful
|
||||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
# nenjim-hub-configuration Specification
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Defines the mandatory deployment configuration that selects which modern Nenjim processes the Hub constructs and starts, while preserving explicit order and fail-fast lifecycle semantics.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
### Requirement: Hub startup requires one fixed runtime configuration
|
|
||||||
The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all configuration parsing and validation before starting Registry or any other process. The path SHALL not be configurable through `Main`, command-line arguments, or the public `NenjimHub` API. A missing, unreadable, or non-regular runtime file SHALL fail Hub startup clearly and identify the configured path. The Hub SHALL not read, copy, generate, or fall back to `conf/NenjimHub.conf.example`, and SHALL not fall back to a hardcoded startup list.
|
|
||||||
|
|
||||||
#### Scenario: Runtime configuration is valid
|
|
||||||
- **WHEN** `conf/NenjimHub.conf` is a readable regular file containing a valid format 1 configuration
|
|
||||||
- **THEN** the Hub derives its complete requested process order from that file before starting Registry
|
|
||||||
|
|
||||||
#### Scenario: Runtime configuration cannot be read
|
|
||||||
- **WHEN** `conf/NenjimHub.conf` is missing, unreadable, or not a regular file
|
|
||||||
- **THEN** startup fails with a diagnostic identifying `conf/NenjimHub.conf` before any process starts
|
|
||||||
|
|
||||||
#### Scenario: Only the example exists
|
|
||||||
- **WHEN** `conf/NenjimHub.conf` is absent but `conf/NenjimHub.conf.example` exists
|
|
||||||
- **THEN** startup fails without reading, copying, or using the example and without starting a hardcoded fallback set
|
|
||||||
|
|
||||||
### Requirement: Format 1 has one exact ordered AUTO_START section
|
|
||||||
The first actual configuration entry SHALL be exactly `FORMAT_VERSION=1`, after optional leading blank lines and full-line comments. Version 1 SHALL be the only supported version; a missing, malformed, duplicate, version 2, or otherwise unsupported declaration SHALL fail. The next actual entry SHALL be exactly one `[AUTO_START]` header, optionally followed only by a trailing `#` comment. Unknown, malformed, repeated, valued, or out-of-order sections and entries before `[AUTO_START]` SHALL fail.
|
|
||||||
|
|
||||||
Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line whose trimmed content begins with `#` SHALL be a full-line comment, and `#` after actual content SHALL begin a trailing comment. Inside `[AUTO_START]`, surrounding whitespace around a process ID SHALL be ignored; full-line and trailing comments SHALL be ignored; comment-only lines SHALL not terminate the section; and the first whitespace-only line or end of file SHALL terminate it. After termination, only blank lines and comments SHALL be accepted. Format 1 SHALL provide no quoting or escaping for `#`.
|
|
||||||
|
|
||||||
#### Scenario: Leading comments and trailing comments are accepted
|
|
||||||
- **WHEN** blank lines or full-line comments precede `FORMAT_VERSION=1`, and the version, section header, or process entries contain trailing `#` comments
|
|
||||||
- **THEN** comment text and permitted surrounding whitespace are ignored while the actual entries are parsed normally
|
|
||||||
|
|
||||||
#### Scenario: Comment inside AUTO_START does not terminate it
|
|
||||||
- **WHEN** a comment-only line appears between two process IDs in `[AUTO_START]`
|
|
||||||
- **THEN** both process IDs remain part of the ordered section
|
|
||||||
|
|
||||||
#### Scenario: Empty line terminates AUTO_START
|
|
||||||
- **WHEN** a whitespace-only line appears after at least one `[AUTO_START]` entry
|
|
||||||
- **THEN** the section terminates at that line and later actual content fails as unexpected content
|
|
||||||
|
|
||||||
#### Scenario: End of file terminates AUTO_START
|
|
||||||
- **WHEN** the final process ID is immediately followed by end of file
|
|
||||||
- **THEN** the section terminates successfully at end of file
|
|
||||||
|
|
||||||
#### Scenario: Version declaration is invalid
|
|
||||||
- **WHEN** the version is absent, malformed, duplicated, not the first actual entry, or has a value other than `1`
|
|
||||||
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line and reason
|
|
||||||
|
|
||||||
#### Scenario: Section structure is invalid
|
|
||||||
- **WHEN** `[AUTO_START]` is absent, duplicated, misspelled, has a value or non-comment trailing token, follows another actual entry, or is replaced by an unknown section
|
|
||||||
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line, offending content and reason
|
|
||||||
|
|
||||||
### Requirement: The complete requested process plan is validated before startup
|
|
||||||
Every active `[AUTO_START]` line SHALL be parsed through the canonical `NenjimComponentId` rules and retained in source order with its line location. Before Registry starts, validation SHALL reject invalid ID syntax, duplicate IDs, unknown IDs, passive-only component IDs, and IDs the reference Hub cannot construct as canonical `NenjimProcess` objects. The section SHALL contain Registry exactly once at position one and Journal exactly once at position two. Applicable failures SHALL identify `conf/NenjimHub.conf`, the relevant line, offending value, and reason.
|
|
||||||
|
|
||||||
#### Scenario: Process ID is malformed
|
|
||||||
- **WHEN** an active section line is not a valid `NenjimComponentId`
|
|
||||||
- **THEN** validation fails before Registry startup and identifies the path, line, offending value and component-ID reason
|
|
||||||
|
|
||||||
#### Scenario: Process ID is duplicated
|
|
||||||
- **WHEN** the same canonical process ID appears more than once
|
|
||||||
- **THEN** validation fails before Registry startup and identifies the duplicate value and line
|
|
||||||
|
|
||||||
#### Scenario: ID is not constructible as a process
|
|
||||||
- **WHEN** an entry is unknown, identifies only a passive component, or is not in the Hub's modern process-construction catalogue
|
|
||||||
- **THEN** validation fails before Registry startup rather than constructing or starting any process
|
|
||||||
|
|
||||||
#### Scenario: Bootstrap entries are not first and second
|
|
||||||
- **WHEN** Registry is absent or not first, or Journal is absent or not second
|
|
||||||
- **THEN** validation fails before Registry startup and identifies the required placement
|
|
||||||
|
|
||||||
### 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 complete current passive component graph needed by selectable processes. 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 complete passive component graph is constructed and registered
|
|
||||||
|
|
||||||
#### Scenario: Passive composition completes
|
|
||||||
- **WHEN** Registry bootstrap succeeds
|
|
||||||
- **THEN** all current 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
|
|
||||||
|
|
||||||
### 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.service.prod`, `evelyn.service.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
|
|
||||||
|
|
||||||
### Requirement: Configured startup is fail-fast and reports actual milestones
|
|
||||||
Registry and every selected process SHALL be registered before its `start()` call and appended to the Hub's immutable ordered startup snapshot only after `start()` returns successfully. Construction failure SHALL leave the selected process unregistered. Registration or startup failure SHALL stop the plan before any later entry is constructed or started; a startup failure MAY leave that process registered but SHALL not record it. Earlier successful processes SHALL remain in snapshots, no rollback or retry SHALL occur, and the Hub SHALL announce its existing online point and enter its blocking runtime wait only after every configured process succeeds.
|
|
||||||
|
|
||||||
#### Scenario: Selected construction fails
|
|
||||||
- **WHEN** construction of the current selected process throws
|
|
||||||
- **THEN** the process is not registered or recorded, no later process is constructed, and the Hub does not announce online or enter its final wait
|
|
||||||
|
|
||||||
#### Scenario: Selected registration fails
|
|
||||||
- **WHEN** registration of the constructed current process throws
|
|
||||||
- **THEN** the process is not started or recorded, no later process is constructed, and no rollback is attempted
|
|
||||||
|
|
||||||
#### Scenario: Selected startup fails
|
|
||||||
- **WHEN** the current process's `start()` throws
|
|
||||||
- **THEN** it remains unrecorded, no later process is constructed or started, earlier successful IDs remain visible, and no rollback is attempted
|
|
||||||
|
|
||||||
#### Scenario: Configured startup completes
|
|
||||||
- **WHEN** all configured process starts return successfully
|
|
||||||
- **THEN** the startup snapshot has the exact configured order, the online point is announced, and the Hub enters its existing blocking wait
|
|
||||||
|
|
||||||
### Requirement: The version-controlled example is a copy-only template
|
|
||||||
The repository SHALL contain `conf/NenjimHub.conf.example` as a documented format 1 copy-only template with only the mandatory Registry and Journal entries active. It SHALL contain no optional deployment-specific process IDs. Its comments SHALL state that the file must be copied to `conf/NenjimHub.conf` before use, Registry and Journal are mandatory, order is preserved, and process dependencies must be listed manually before their consumers. Documentation SHALL show the same minimal bootstrap as a headless example and SHALL describe recursive dependency construction as future work rather than implemented behavior.
|
|
||||||
|
|
||||||
#### Scenario: Example provides a generic minimal bootstrap
|
|
||||||
- **WHEN** the example's uncommented process IDs are read in order
|
|
||||||
- **THEN** they are only Registry and Journal, with no optional deployment-specific process ID present
|
|
||||||
|
|
||||||
#### Scenario: Operator uses the minimal headless example
|
|
||||||
- **WHEN** the documented headless configuration is copied to the runtime path
|
|
||||||
- **THEN** it selects only Registry and Journal and leaves every optional process for the operator to add explicitly
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
# solana-wallet-signing Specification
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Define how a reference Solana wallet proves during construction that its external signer is compatible, available, and bound to the configured wallet address.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
@@ -58,15 +58,34 @@ import java.util.List;
|
|||||||
* repeats the completed burn.</li>
|
* repeats the completed burn.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* <p>One dedicated worker begins immediately when the Hub starts the reference
|
* <p>One dedicated worker begins immediately on {@link #start()}, uses fixed
|
||||||
* implementation through its separate {@code NenjimProcess} view, uses fixed
|
|
||||||
* delay measured after each completed iteration, and never overlaps
|
* delay measured after each completed iteration, and never overlaps
|
||||||
* iterations. This service view currently exposes no domain operations and no
|
* iterations. {@link #stop()} prevents new iterations and stops that worker.
|
||||||
* lifecycle control. Pending signatures and swap/burn suspension flags are
|
* Pending signatures and swap/burn suspension flags are process-local only
|
||||||
* process-local only and are reset by a process restart. Such a reset forgets
|
* and are deliberately reset by a completed explicit stop/start cycle or a
|
||||||
* unknown transaction state and can weaken duplicate prevention; operators
|
* process restart. Such a reset forgets unknown transaction state and can
|
||||||
* should reconcile uncertain on-chain activity before deliberately restarting
|
* weaken duplicate prevention; operators should reconcile uncertain on-chain
|
||||||
* a suspended or pending process.</p>
|
* activity before deliberately restarting a suspended or pending service.</p>
|
||||||
*/
|
*/
|
||||||
public interface EvelynIOUBurnerService extends NenjimComponent {
|
public interface EvelynIOUBurnerService extends NenjimComponent {
|
||||||
|
/**
|
||||||
|
* Starts the dedicated worker and begins the first iteration immediately.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if this instance is already running or a
|
||||||
|
* prior worker has not terminated cleanly
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents new iterations, interrupts current processing, and stops the
|
||||||
|
* dedicated worker cleanly.
|
||||||
|
*
|
||||||
|
* <p>A completed stop clears process-local pending and suspension state.
|
||||||
|
* If the calling thread is interrupted while waiting for termination, its
|
||||||
|
* interrupt status is preserved.</p>
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if clean worker termination cannot be
|
||||||
|
* established
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-6
@@ -46,9 +46,7 @@ import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
|
|||||||
import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_MINT_NAMESPACE;
|
import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_MINT_NAMESPACE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference Evelyn IOU burner whose domain service view is exposed through
|
* Reference implementation of the Evelyn IOU buyback-and-burn lifecycle.
|
||||||
* {@link EvelynIOUBurnerService} and whose startup is owned by the Hub through
|
|
||||||
* {@link NenjimProcess}.
|
|
||||||
*/
|
*/
|
||||||
public final class EvelynIOUBurnerServiceImpl
|
public final class EvelynIOUBurnerServiceImpl
|
||||||
implements EvelynIOUBurnerService, NenjimProcess {
|
implements EvelynIOUBurnerService, NenjimProcess {
|
||||||
@@ -204,9 +202,9 @@ public final class EvelynIOUBurnerServiceImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void start() {
|
public synchronized void start() {
|
||||||
if (worker != null) {
|
if (worker != null || stopping) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Evelyn IOU burner is already running"
|
"Evelyn IOU burner is already running or stopping"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,12 +247,70 @@ public final class EvelynIOUBurnerServiceImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
ScheduledExecutorService workerToStop;
|
||||||
|
synchronized (this) {
|
||||||
|
if (worker == null) {
|
||||||
|
resetProcessLocalState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stopping) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Evelyn IOU burner is already stopping"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
stopping = true;
|
||||||
|
workerToStop = worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
workerToStop.shutdownNow();
|
||||||
|
boolean terminated;
|
||||||
|
try {
|
||||||
|
terminated = workerToStop.awaitTermination(
|
||||||
|
WORKER_TERMINATION_TIMEOUT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
);
|
||||||
|
} catch (InterruptedException exception) {
|
||||||
|
workerToStop.shutdownNow();
|
||||||
|
boolean terminatedAfterInterruption = workerToStop.isTerminated();
|
||||||
|
synchronized (this) {
|
||||||
|
if (terminatedAfterInterruption && worker == workerToStop) {
|
||||||
|
worker = null;
|
||||||
|
resetProcessLocalState();
|
||||||
|
}
|
||||||
|
stopping = false;
|
||||||
|
}
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Interrupted while stopping Evelyn IOU burner",
|
||||||
|
exception
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (!terminated) {
|
||||||
|
stopping = false;
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Evelyn IOU burner worker did not terminate cleanly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (worker == workerToStop) {
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
resetProcessLocalState();
|
||||||
|
stopping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void runIterationSafely() {
|
private void runIterationSafely() {
|
||||||
try {
|
try {
|
||||||
runIteration();
|
runIteration();
|
||||||
} catch (InterruptedException exception) {
|
} catch (InterruptedException exception) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
log.warn("Evelyn IOU burner iteration was interrupted");
|
if (!isStopping()) {
|
||||||
|
log.warn("Evelyn IOU burner iteration was interrupted");
|
||||||
|
}
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
log.error(
|
log.error(
|
||||||
"Unexpected Evelyn IOU burner iteration failure",
|
"Unexpected Evelyn IOU burner iteration failure",
|
||||||
@@ -935,6 +991,10 @@ public final class EvelynIOUBurnerServiceImpl
|
|||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized boolean isStopping() {
|
||||||
|
return stopping;
|
||||||
|
}
|
||||||
|
|
||||||
private void resetProcessLocalState() {
|
private void resetProcessLocalState() {
|
||||||
pendingBurn = null;
|
pendingBurn = null;
|
||||||
swapsSuspended = false;
|
swapsSuspended = false;
|
||||||
@@ -951,6 +1011,7 @@ public final class EvelynIOUBurnerServiceImpl
|
|||||||
private static final int MAXIMUM_SLIPPAGE_BPS = 10000;
|
private static final int MAXIMUM_SLIPPAGE_BPS = 10000;
|
||||||
private static final ΩamountDecimalsΩ NATIVE_SOLANA_DECIMALS = 9;
|
private static final ΩamountDecimalsΩ NATIVE_SOLANA_DECIMALS = 9;
|
||||||
private static final ΩamountDecimalsΩ MAXIMUM_SPL_TOKEN_DECIMALS = 255;
|
private static final ΩamountDecimalsΩ MAXIMUM_SPL_TOKEN_DECIMALS = 255;
|
||||||
|
private static final long WORKER_TERMINATION_TIMEOUT_SECONDS = 10;
|
||||||
private static final String WORKER_THREAD_NAME =
|
private static final String WORKER_THREAD_NAME =
|
||||||
"evelyn-iou-buyback-and-burner";
|
"evelyn-iou-buyback-and-burner";
|
||||||
|
|
||||||
@@ -971,6 +1032,7 @@ public final class EvelynIOUBurnerServiceImpl
|
|||||||
private final TradingPair eveUsdtTradingPair;
|
private final TradingPair eveUsdtTradingPair;
|
||||||
|
|
||||||
private ScheduledExecutorService worker;
|
private ScheduledExecutorService worker;
|
||||||
|
private boolean stopping;
|
||||||
private boolean swapsSuspended;
|
private boolean swapsSuspended;
|
||||||
private boolean burnsSuspended;
|
private boolean burnsSuspended;
|
||||||
private PendingBurn pendingBurn;
|
private PendingBurn pendingBurn;
|
||||||
|
|||||||
@@ -6,13 +6,21 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides price observations for trading pairs.
|
* Provides price observations for trading pairs.
|
||||||
*
|
|
||||||
* <p>The service contract contains only operations for ordinary consumers.
|
|
||||||
* The Hub owns startup through the implementation's separate
|
|
||||||
* {@code NenjimProcess} view.</p>
|
|
||||||
*/
|
*/
|
||||||
public interface TickerService extends NenjimComponent {
|
public interface TickerService extends NenjimComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the ticker service.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the service cannot be started
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops every source started by this service.
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the latest available price observation for a trading pair.
|
* Returns the latest available price observation for a trading pair.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference Ticker implementation whose domain API is exposed through
|
|
||||||
* {@link TickerService} and whose startup is owned by the Hub through
|
|
||||||
* {@link NenjimProcess}.
|
|
||||||
*/
|
|
||||||
public final class TickerServiceImpl implements
|
public final class TickerServiceImpl implements
|
||||||
TickerService,
|
TickerService,
|
||||||
PriceSink,
|
PriceSink,
|
||||||
@@ -93,6 +88,24 @@ public final class TickerServiceImpl implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stop() {
|
||||||
|
if (state == LifecycleState.NEW || state == LifecycleState.STOPPED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state != LifecycleState.STARTED) {
|
||||||
|
throw new IllegalStateException("Ticker service cannot stop while in state " + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = LifecycleState.STOPPING;
|
||||||
|
RuntimeException failure = stopStartedSources();
|
||||||
|
resetStoppedState();
|
||||||
|
|
||||||
|
if (failure != null) {
|
||||||
|
throw failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized @NotNull PriceObservation getLatestPrice(
|
public synchronized @NotNull PriceObservation getLatestPrice(
|
||||||
@NotNull TradingPair tradingPair
|
@NotNull TradingPair tradingPair
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.r35157.cryptowallet.solana.impl.ref;
|
package com.r35157.cryptowallet.solana.impl.ref;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||||
import com.r35157.cryptowallet.solana.MissingRecipientTokenAccountPolicy;
|
import com.r35157.cryptowallet.solana.MissingRecipientTokenAccountPolicy;
|
||||||
@@ -29,202 +28,9 @@ public class SolanaWalletImpl implements SolanaWallet {
|
|||||||
String signerKeyName,
|
String signerKeyName,
|
||||||
SolanaBlockChain solanaBlockChain
|
SolanaBlockChain solanaBlockChain
|
||||||
) {
|
) {
|
||||||
if (address == null || address.isBlank()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Solana wallet address must not be blank"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (signerKeyName == null || signerKeyName.isBlank()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Solana wallet signer key name must not be blank"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.signerKeyName = signerKeyName;
|
this.signerKeyName = signerKeyName;
|
||||||
this.solanaBlockChain = Objects.requireNonNull(
|
this.solanaBlockChain = solanaBlockChain;
|
||||||
solanaBlockChain,
|
|
||||||
"Solana blockchain must not be null"
|
|
||||||
);
|
|
||||||
|
|
||||||
validateJupConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateJupConfiguration() {
|
|
||||||
JupCommandResult versionResult = runJupCommand(
|
|
||||||
"jup --version",
|
|
||||||
"--version"
|
|
||||||
);
|
|
||||||
String actualVersion = versionResult.standardOutput().trim();
|
|
||||||
|
|
||||||
if (!actualVersion.matches("\\d+\\.\\d+\\.\\d+")) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Invalid jup version while validating Solana wallet "
|
|
||||||
+ "signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "': expected version "
|
|
||||||
+ EXPECTED_JUP_VERSION
|
|
||||||
+ " but jup --version returned '"
|
|
||||||
+ actualVersion
|
|
||||||
+ "'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// An exact match is intentional: the CLI arguments and JSON response
|
|
||||||
// structure used by this class are verified only for this version.
|
|
||||||
if (!EXPECTED_JUP_VERSION.equals(actualVersion)) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unsupported jup version while validating Solana wallet "
|
|
||||||
+ "signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "': expected "
|
|
||||||
+ EXPECTED_JUP_VERSION
|
|
||||||
+ " but found "
|
|
||||||
+ actualVersion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
JupCommandResult keysResult = runJupCommand(
|
|
||||||
"jup -f json keys list",
|
|
||||||
"-f",
|
|
||||||
"json",
|
|
||||||
"keys",
|
|
||||||
"list"
|
|
||||||
);
|
|
||||||
JsonNode keys;
|
|
||||||
|
|
||||||
try {
|
|
||||||
keys = OBJECT_MAPPER.readTree(keysResult.standardOutput());
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unable to decode the jup key list while validating "
|
|
||||||
+ "Solana wallet signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "'",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys == null || !keys.isArray()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"jup returned an invalid key list while validating "
|
|
||||||
+ "Solana wallet signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "': expected a JSON array"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonNode signerKey = null;
|
|
||||||
for (JsonNode key : keys) {
|
|
||||||
if (signerKeyName.equals(key.path("name").textValue())) {
|
|
||||||
signerKey = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signerKey == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Solana wallet signer key '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "' is not available to jup"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ΩSolanaWalletIdΩ signerAddress = signerKey
|
|
||||||
.path("address")
|
|
||||||
.textValue();
|
|
||||||
if (signerAddress == null || signerAddress.isBlank()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"jup signer key '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "' does not contain a valid address; expected "
|
|
||||||
+ address
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!address.equals(signerAddress)) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"jup signer key '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "' has an unexpected address: expected "
|
|
||||||
+ address
|
|
||||||
+ " but found "
|
|
||||||
+ signerAddress
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JupCommandResult runJupCommand(
|
|
||||||
String commandDescription,
|
|
||||||
String... arguments
|
|
||||||
) {
|
|
||||||
String[] command = new String[arguments.length + 1];
|
|
||||||
command[0] = "jup";
|
|
||||||
System.arraycopy(arguments, 0, command, 1, arguments.length);
|
|
||||||
|
|
||||||
Process process;
|
|
||||||
try {
|
|
||||||
process = new ProcessBuilder(command).start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unable to start required jup CLI command '"
|
|
||||||
+ commandDescription
|
|
||||||
+ "' while validating Solana wallet signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "'. Ensure jup version "
|
|
||||||
+ EXPECTED_JUP_VERSION
|
|
||||||
+ " is installed and available on PATH",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String standardOutput = new String(
|
|
||||||
process.getInputStream().readAllBytes(),
|
|
||||||
StandardCharsets.UTF_8
|
|
||||||
);
|
|
||||||
String standardError = new String(
|
|
||||||
process.getErrorStream().readAllBytes(),
|
|
||||||
StandardCharsets.UTF_8
|
|
||||||
);
|
|
||||||
int exitCode = process.waitFor();
|
|
||||||
|
|
||||||
if (exitCode != 0) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Required jup CLI command '"
|
|
||||||
+ commandDescription
|
|
||||||
+ "' failed while validating Solana wallet "
|
|
||||||
+ "signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "' with exit code "
|
|
||||||
+ exitCode
|
|
||||||
+ ": "
|
|
||||||
+ standardError.trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JupCommandResult(standardOutput);
|
|
||||||
} catch (IOException e) {
|
|
||||||
process.destroyForcibly();
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unable to read the result of required jup CLI command '"
|
|
||||||
+ commandDescription
|
|
||||||
+ "' while validating Solana wallet signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "'",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
process.destroyForcibly();
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Interrupted while running required jup CLI command '"
|
|
||||||
+ commandDescription
|
|
||||||
+ "' for Solana wallet signer '"
|
|
||||||
+ signerKeyName
|
|
||||||
+ "'",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -669,10 +475,6 @@ public class SolanaWalletImpl implements SolanaWallet {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record JupCommandResult(String standardOutput) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String EXPECTED_JUP_VERSION = "0.10.1";
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
private final ΩSolanaWalletIdΩ address;
|
private final ΩSolanaWalletIdΩ address;
|
||||||
|
|||||||
@@ -76,37 +76,43 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
"NenjimHub start has already been attempted");
|
"NenjimHub start has already been attempted");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<NenjimComponentId, ProcessConstructor> processConstructors = createProcessConstructorCatalogue();
|
Map<NenjimComponentId, ProcessConstructor> processConstructors =
|
||||||
Set<NenjimComponentId> knownProcessIds = selectableProcessIds(processConstructors);
|
createProcessConstructorCatalogue();
|
||||||
|
NenjimHubConfiguration configuration =
|
||||||
|
NenjimHubConfigurationParser.parse(
|
||||||
|
HUB_CONFIGURATION_PATH,
|
||||||
|
selectableProcessIds(processConstructors),
|
||||||
|
REGISTRY_SERVICE_ID,
|
||||||
|
JOURNAL_SERVICE_ID);
|
||||||
|
|
||||||
log.debug("Parsing Nenjim configuration...");
|
log.info("Initializing Nenjim Registry...");
|
||||||
configuration = NenjimHubConfigurationParser.parse(
|
NenjimRegistryAssembly registryAssembly =
|
||||||
HUB_CONFIGURATION_PATH,
|
NenjimRegistryAssembly.create();
|
||||||
knownProcessIds,
|
registryService =
|
||||||
REGISTRY_SERVICE_ID,
|
registryAssembly.registryService();
|
||||||
JOURNAL_SERVICE_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
log.debug("Initializing Nenjim Registry...");
|
|
||||||
NenjimRegistryAssembly registryAssembly = NenjimRegistryAssembly.create();
|
|
||||||
registryService = registryAssembly.registryService();
|
|
||||||
NenjimProcess registryProcess = registryAssembly.registryProcess();
|
NenjimProcess registryProcess = registryAssembly.registryProcess();
|
||||||
registryServiceManager = registryAssembly.registryServiceManager();
|
registryServiceManager = registryAssembly.registryServiceManager();
|
||||||
|
|
||||||
registryProcess.start();
|
registryProcess.start();
|
||||||
registryServiceManager.registerComponent(
|
registryServiceManager.registerComponent(
|
||||||
REGISTRY_SERVICE_ID,
|
REGISTRY_SERVICE_ID,
|
||||||
registryService
|
registryService);
|
||||||
);
|
|
||||||
recordStartedProcess(REGISTRY_SERVICE_ID);
|
recordStartedProcess(REGISTRY_SERVICE_ID);
|
||||||
|
|
||||||
log.debug("Initializing passive Nenjim components...");
|
log.info("Initializing passive Nenjim components...");
|
||||||
initializeAndRegisterPassiveComponents(registryServiceManager);
|
initializeAndRegisterPassiveComponents(registryServiceManager);
|
||||||
|
|
||||||
log.info("Auto-starting configured Nenjim processes...");
|
log.info("Constructing and starting configured Nenjim processes...");
|
||||||
autoStartProcesses(processConstructors);
|
for (NenjimComponentId componentId
|
||||||
|
: configuration.autoStartProcessIds().subList(
|
||||||
|
1,
|
||||||
|
configuration.autoStartProcessIds().size())) {
|
||||||
|
constructRegisterAndStartProcess(
|
||||||
|
componentId,
|
||||||
|
processConstructors);
|
||||||
|
}
|
||||||
|
|
||||||
log.info("Done - Now online!");
|
System.out.println("Done - Now online!");
|
||||||
|
|
||||||
waitForAndShutdown();
|
waitForAndShutdown();
|
||||||
}
|
}
|
||||||
@@ -117,20 +123,6 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
return List.copyOf(startedProcessIds);
|
return List.copyOf(startedProcessIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoStartProcesses(Map<NenjimComponentId, ProcessConstructor> processConstructors)
|
|
||||||
throws Exception {
|
|
||||||
List<NenjimComponentId> autoStartProcessIds = configuration
|
|
||||||
.autoStartProcessIds()
|
|
||||||
.subList(1, configuration.autoStartProcessIds().size());
|
|
||||||
|
|
||||||
for (NenjimComponentId componentId : autoStartProcessIds) {
|
|
||||||
constructRegisterAndStartProcess(
|
|
||||||
componentId,
|
|
||||||
processConstructors
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private NenjimProcess constructJournalProcess() {
|
private NenjimProcess constructJournalProcess() {
|
||||||
JournalServiceAssembly journalAssembly =
|
JournalServiceAssembly journalAssembly =
|
||||||
JournalServiceAssembly.create(JOURNAL_ROOT);
|
JournalServiceAssembly.create(JOURNAL_ROOT);
|
||||||
@@ -140,17 +132,12 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
|
|
||||||
private void initializeAndRegisterPassiveComponents(
|
private void initializeAndRegisterPassiveComponents(
|
||||||
NenjimRegistryServiceManager registryServiceManager) {
|
NenjimRegistryServiceManager registryServiceManager) {
|
||||||
log.debug("Initializing components...");
|
initializeBuildingBlocks(registryServiceManager);
|
||||||
initializeComponents(registryServiceManager);
|
|
||||||
|
|
||||||
log.debug("Initializing price sources...");
|
|
||||||
initializePriceSources(registryServiceManager);
|
initializePriceSources(registryServiceManager);
|
||||||
|
|
||||||
log.debug("Initializing passive services...");
|
|
||||||
initializePassiveServices(registryServiceManager);
|
initializePassiveServices(registryServiceManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeComponents(
|
private void initializeBuildingBlocks(
|
||||||
NenjimRegistryServiceManager registryServiceManager) {
|
NenjimRegistryServiceManager registryServiceManager) {
|
||||||
objectCache = new ObjectCacheImpl();
|
objectCache = new ObjectCacheImpl();
|
||||||
registryServiceManager.registerComponent(OBJECT_CACHE_ID, objectCache);
|
registryServiceManager.registerComponent(OBJECT_CACHE_ID, objectCache);
|
||||||
@@ -372,20 +359,33 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
return Map.ofEntries(
|
return Map.ofEntries(
|
||||||
Map.entry(JOURNAL_SERVICE_ID, this::constructJournalProcess),
|
Map.entry(JOURNAL_SERVICE_ID, this::constructJournalProcess),
|
||||||
Map.entry(TICKER_SERVICE_ID, this::constructTickerProcess),
|
Map.entry(TICKER_SERVICE_ID, this::constructTickerProcess),
|
||||||
Map.entry(JUPITER_PERPS_ALARM_ID, this::constructJupiterPerpsAlarmProcess),
|
Map.entry(
|
||||||
Map.entry(EVELYN_SERVICE_PROD_ID, this::constructEvelynProdProcess),
|
JUPITER_PERPS_ALARM_ID,
|
||||||
Map.entry(EVELYN_SERVICE_TEST_ID, this::constructEvelynTestProcess),
|
this::constructJupiterPerpsAlarmProcess),
|
||||||
Map.entry(EVELYN_IOU_BURNER_PROD_ID, this::constructEvelynIOUBurnerProdProcess),
|
Map.entry(
|
||||||
Map.entry(JAVAFX_RUNTIME_SERVICE_ID, this::constructJavaFxRuntimeProcess),
|
EVELYN_SERVICE_PROD_ID,
|
||||||
Map.entry(EVELYN_MISSION_CONTROL_ID, this::constructMissionControlProcess),
|
this::constructEvelynProdProcess),
|
||||||
Map.entry(PROCESS_MANAGER_ID, this::constructProcessManagerProcess)
|
Map.entry(
|
||||||
);
|
EVELYN_SERVICE_TEST_ID,
|
||||||
|
this::constructEvelynTestProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_IOU_BURNER_PROD_ID,
|
||||||
|
this::constructEvelynIOUBurnerProdProcess),
|
||||||
|
Map.entry(
|
||||||
|
JAVAFX_RUNTIME_SERVICE_ID,
|
||||||
|
this::constructJavaFxRuntimeProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
|
this::constructMissionControlProcess),
|
||||||
|
Map.entry(
|
||||||
|
PROCESS_MANAGER_ID,
|
||||||
|
this::constructProcessManagerProcess));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<NenjimComponentId> selectableProcessIds(
|
private static Set<NenjimComponentId> selectableProcessIds(
|
||||||
Map<NenjimComponentId, ProcessConstructor> processConstructors
|
Map<NenjimComponentId, ProcessConstructor> processConstructors) {
|
||||||
) {
|
Set<NenjimComponentId> processIds =
|
||||||
Set<NenjimComponentId> processIds = new HashSet<>(processConstructors.keySet());
|
new HashSet<>(processConstructors.keySet());
|
||||||
processIds.add(REGISTRY_SERVICE_ID);
|
processIds.add(REGISTRY_SERVICE_ID);
|
||||||
return Set.copyOf(processIds);
|
return Set.copyOf(processIds);
|
||||||
}
|
}
|
||||||
@@ -416,8 +416,6 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
+ "' is not registered as "
|
+ "' is not registered as "
|
||||||
+ NenjimProcess.class.getName());
|
+ NenjimProcess.class.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Starting process '" + componentId.value() + "'...");
|
|
||||||
process.start();
|
process.start();
|
||||||
recordStartedProcess(componentId);
|
recordStartedProcess(componentId);
|
||||||
}
|
}
|
||||||
@@ -535,7 +533,4 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
private JavaFxRuntimeService javaFxRuntimeService;
|
private JavaFxRuntimeService javaFxRuntimeService;
|
||||||
private EvelynMissionControl missionControl;
|
private EvelynMissionControl missionControl;
|
||||||
private NenjimProcessManager processManager;
|
private NenjimProcessManager processManager;
|
||||||
|
|
||||||
// Internal state
|
|
||||||
private NenjimHubConfiguration configuration;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package crypto.r35157.cauldron;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.logging.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
//import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class TickerService {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
configureLogging();
|
||||||
|
|
||||||
|
logger.info("Initializing TickerService:");
|
||||||
|
logger.info(" Starting embedded SQLLite...");
|
||||||
|
configureDatabase();
|
||||||
|
|
||||||
|
logger.info(" Validating schema...");
|
||||||
|
validateSchema();
|
||||||
|
|
||||||
|
logger.info(" Starting HTTP Service on port 8123...");
|
||||||
|
configureHTTPService();
|
||||||
|
|
||||||
|
logger.info("Ready to service!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateSchema() throws SQLException {
|
||||||
|
boolean exist = doTableExist(dbConnection, "ticks");
|
||||||
|
if(exist == false) {
|
||||||
|
createTable(dbConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean doTableExist(Connection dbConnection, String tableName) throws SQLException {
|
||||||
|
String sql = "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
|
||||||
|
|
||||||
|
PreparedStatement pstmt = dbConnection.prepareStatement(sql);
|
||||||
|
pstmt.setString(1, tableName);
|
||||||
|
ResultSet rs = pstmt.executeQuery();
|
||||||
|
|
||||||
|
return rs.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureLogging() {
|
||||||
|
try {
|
||||||
|
Formatter logFormatter = new CustomLogFormatter();
|
||||||
|
|
||||||
|
// Remove the default console handler
|
||||||
|
Logger rootLogger = Logger.getLogger("");
|
||||||
|
rootLogger.removeHandler(rootLogger.getHandlers()[0]);
|
||||||
|
|
||||||
|
// Add a custom console handler
|
||||||
|
ConsoleHandler consoleHandler = new ConsoleHandler();
|
||||||
|
consoleHandler.setLevel(Level.ALL);
|
||||||
|
consoleHandler.setFormatter(logFormatter);
|
||||||
|
rootLogger.addHandler(consoleHandler);
|
||||||
|
|
||||||
|
// Add a file handler
|
||||||
|
FileHandler fileHandler = new FileHandler("TickerService.log", true);
|
||||||
|
fileHandler.setLevel(Level.ALL);
|
||||||
|
fileHandler.setFormatter(logFormatter);
|
||||||
|
rootLogger.addHandler(fileHandler);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.severe("Failed to set up logging: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureDatabase() throws SQLException {
|
||||||
|
dbConnection = DriverManager.getConnection(DB_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createTable(Connection dbConnection) throws SQLException {
|
||||||
|
String sql = "CREATE TABLE ticks (" +
|
||||||
|
"epoch INTEGER NOT NULL," +
|
||||||
|
"baseAssetId TEXT NOT NULL," +
|
||||||
|
"quoteAssetId TEXT NOT NULL," +
|
||||||
|
"price REAL NOT NULL," +
|
||||||
|
"PRIMARY KEY (epoch, baseAssetId, quoteAssetId)" +
|
||||||
|
");";
|
||||||
|
|
||||||
|
Statement stmt = dbConnection.createStatement();
|
||||||
|
stmt.execute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void insertStockPrice(Connection conn, String symbol, double price) throws SQLException {
|
||||||
|
String sql = "INSERT INTO stock_prices(symbol, price) VALUES(?, ?)";
|
||||||
|
|
||||||
|
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||||
|
pstmt.setString(1, symbol);
|
||||||
|
pstmt.setDouble(2, price);
|
||||||
|
pstmt.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void retrieveStockPrices(Connection conn, String symbol) {
|
||||||
|
String sql = "SELECT id, symbol, price, timestamp FROM stock_prices WHERE symbol = ?";
|
||||||
|
|
||||||
|
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||||
|
pstmt.setString(1, symbol);
|
||||||
|
ResultSet rs = pstmt.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
System.out.println(rs.getInt("id") + "\t" +
|
||||||
|
rs.getString("symbol") + "\t" +
|
||||||
|
rs.getDouble("price") + "\t" +
|
||||||
|
rs.getString("timestamp"));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureHTTPService() throws Exception{
|
||||||
|
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), BACKLOG);
|
||||||
|
server.createContext("/ticker", new MyHandler());
|
||||||
|
server.setExecutor(null); // Creates a default executor
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomLogFormatter extends Formatter {
|
||||||
|
@Override
|
||||||
|
public String format(LogRecord record) {
|
||||||
|
return String.format("%1$s [%2$s] %3$s%n",
|
||||||
|
record.getLevel().getName(),
|
||||||
|
record.getLoggerName(),
|
||||||
|
record.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyHandler implements HttpHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
// TODO: Reenable when import is fixed
|
||||||
|
/*
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("message", "Hello, World!");
|
||||||
|
String response = json.toString();
|
||||||
|
|
||||||
|
exchange.getResponseHeaders().set("Content-type", RESPONSE_MIMETYPE);
|
||||||
|
exchange.sendResponseHeaders(HTTP_OK, response.length());
|
||||||
|
OutputStream os = exchange.getResponseBody();
|
||||||
|
os.write(response.getBytes());
|
||||||
|
|
||||||
|
os.close();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(TickerService.class.getName());
|
||||||
|
private static Connection dbConnection;
|
||||||
|
|
||||||
|
private static final int PORT = 8123;
|
||||||
|
private static final int BACKLOG = 10;
|
||||||
|
private static final String RESPONSE_MIMETYPE = "application/json";
|
||||||
|
private static final int HTTP_OK = 200;
|
||||||
|
private static final String DB_URL = "jdbc:sqlite:ticker.db";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user