88: Rename Evelyn to EvelynStatusService and expose the calculated reference price
This commit is contained in:
@@ -49,6 +49,20 @@ Nenjim Hub reads only `conf/NenjimHub.conf`; copy
|
||||
`conf/NenjimHub.conf.example` before first use and edit the ordered process
|
||||
list for the deployment role. The example is never loaded as runtime input.
|
||||
|
||||
The Evelyn status processes use `evelyn.status.prod` and `evelyn.status.test`.
|
||||
When upgrading an existing deployment, replace `evelyn.service.prod` and
|
||||
`evelyn.service.test` with those IDs in private `conf/NenjimHub.conf`, preserving
|
||||
their order and enabled/commented selections. The instance names `Production`
|
||||
and `Test`, their `data/evelyn/<instance-name>/status.log` files, and history
|
||||
format version 1 remain unchanged.
|
||||
|
||||
`com.r35157.evelyn.service.status.EvelynStatusService` exposes immutable status
|
||||
history and `AssetPrice getReferencePrice(Instant timestamp)`. The reference
|
||||
price is calculated on demand for the supplied instant and carries the canonical
|
||||
EVE/USDT trading pair. It works before startup and while sampling is stopped,
|
||||
without accessing Ticker or history storage. See [Evelyn](docs/Evelyn.md) for the
|
||||
curve and service contract.
|
||||
|
||||
## Release log:
|
||||
| Date | Version | Description |
|
||||
|:-----------------|:--------------:|----------------------|
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
* [8. Risici, fail-safes og antifragilitet](#8-risici-fail-safes-og-antifragilitet)
|
||||
* [9. Udvikling, YouTrack-integration og offentlig gennemsigtighed](#9-udvikling-youtrack-integration-og-offentlig-gennemsigtighed)
|
||||
* [10. Fremtidige emner / TODO](#10-fremtidige-emner--todo)
|
||||
* [11. Statusservice og referencepris](#11-statusservice-og-referencepris)
|
||||
<!-- TOC -->
|
||||
|
||||
## 1. Introduktion formål og vision
|
||||
@@ -301,3 +302,35 @@ kapitalallokering over tid (AI-drevet tuning).
|
||||
ekstreme markedsbevægelser.
|
||||
- Open API for ekstern integration: Gør det muligt at tilføje eksterne datafeeds, udveksle signaler med andre
|
||||
systemer, eller automatisere funding-opgaver.
|
||||
|
||||
## 11. Statusservice og referencepris
|
||||
|
||||
Evelyn er navnet på det samlede system. `com.r35157.evelyn.service.status.EvelynStatusService` indsamler og
|
||||
persisterer statusindeks for én navngiven instans og giver consumers adgang til den samme referencepris, som
|
||||
Evelyn Price Index bruger. Referenceimplementeringen er `EvelynStatusServiceImpl` under
|
||||
`com.r35157.evelyn.service.status.impl.ref`. Hub'en ejer fortsat startup af de enkelte processer.
|
||||
|
||||
`AssetPrice getReferencePrice(Instant timestamp)` beregner prisen for det angivne tidspunkt:
|
||||
|
||||
```text
|
||||
Start: 2026-07-31T22:00:00Z
|
||||
Startpris: 15.00 USDT pr. EVE
|
||||
Årlig faktor: 1.20
|
||||
Årslængde: 31,536,000 sekunder
|
||||
referencePrice = 15.00 × 1.20^(elapsedSeconds / 31,536,000)
|
||||
```
|
||||
|
||||
Resultatet indeholder det kanoniske EVE/USDT-tradingpar, så quote-valutaen er eksplicit. Prisen er 15 ved start og
|
||||
18 efter ét 365-dages år. Statusindekset bruger `((actualPrice / referencePrice) - 1) × 10` med samme samplingtidspunkt
|
||||
og samme beregning og konstanter. Den eksisterende konvention om at behandle USDT som USD i indekset bevares.
|
||||
|
||||
Referenceprisen beregnes ved hvert kald uden tickeropslag, historikadgang, workers eller ændring af historikken.
|
||||
Kaldet virker før sampling starter og mens den er stoppet, også uden provisionerede historikfiler. `null` afvises
|
||||
med `NullPointerException`, og tidspunkter før kurvens start afvises med `IllegalArgumentException`.
|
||||
|
||||
Production og Test bevarer separate navne, historikker, livscyklusser og Mission Control-charts. Historikfilerne
|
||||
ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1. Sampling sker fortsat med cirka ét
|
||||
minuts fast forsinkelse, og et punkt persisteres før publicering i et immutable snapshot. `stop()` rydder kun
|
||||
hukommelsen og beholder navnereservationen, restart genindlæser filen, og `close()` frigiver navnet efter sikker
|
||||
terminering. `EvelynStatusIndexPoint` bliver i sin eksisterende package med alle seks indeksfelter; kun Evelyn
|
||||
Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy-rebalancering er fortsat inaktiv.
|
||||
|
||||
+9
-2
@@ -268,14 +268,21 @@ nenjim.registry.service
|
||||
nenjim.journal.service
|
||||
assetaz.ticker.default
|
||||
jupiter-perps-alarm.default
|
||||
evelyn.service.prod
|
||||
evelyn.service.test
|
||||
evelyn.status.prod
|
||||
evelyn.status.test
|
||||
evelyn.iou-burner.prod
|
||||
nenjim.javafx-runtime.service
|
||||
evelyn.mission-control.default
|
||||
nenjim.process-manager.default
|
||||
```
|
||||
|
||||
Ved opgradering erstattes `evelyn.service.prod` med `evelyn.status.prod` og
|
||||
`evelyn.service.test` med `evelyn.status.test` i deploymentets private `conf/NenjimHub.conf`, hvor de forekommer.
|
||||
Bevar rækkefølge og aktive/udkommenterede valg. De gamle ID'er accepteres ikke længere. Hub'en konstruerer samme
|
||||
navngivne `Production`- og `Test`-instanser gennem `EvelynStatusServiceImpl` og injicerer deres
|
||||
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
||||
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
||||
|
||||
Efter validering konstruerer og starter Hub'en Registry, registrerer det som `nenjim.registry.service` og registrerer
|
||||
startup-milepælen. Derefter konstrueres og registreres hele den nuværende passive graph uden at starte workers eller
|
||||
processer. Først herefter behandles resten af `[AUTO_START]` i den angivne rækkefølge, begyndende med Journal. Hver
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
| 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. |
|
||||
| EvelynStatusService | Service for én navngiven Evelyn-instans, som indsamler og persisterer statushistorik og udleverer den beregnede EVE-referencepris med eksplicit USDT-quote. Den bevarer sit eksisterende start/stop/close-contract; samme `EvelynStatusServiceImpl`-objekt startes af Hub'en som `NenjimProcess` under `evelyn.status.prod` eller `evelyn.status.test`. Evelyn er fortsat navnet på det samlede system. |
|
||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Provide typed process-local Evelyn status measurements and visualize the current
|
||||
## Requirements
|
||||
|
||||
### Requirement: Evelyn calculates the live price index
|
||||
Evelyn SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant t, on or after 2026-07-31T22:00:00Z, the expected price SHALL be 15 × 1.20^(elapsedSeconds / 31,536,000) and the Evelyn Price Index SHALL be ((actualPrice / expectedPrice) - 1) × 10, calculated with adequate decimal precision and without arbitrary history rounding.
|
||||
`EvelynStatusService` SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant t, on or after 2026-07-31T22:00:00Z, the reference price SHALL be 15 × 1.20^(elapsedSeconds / 31,536,000) and the Evelyn Price Index SHALL be ((actualPrice / referencePrice) - 1) × 10, calculated with adequate decimal precision and without arbitrary history rounding. Sampling and the public reference-price operation SHALL use the same reference calculation and constants at the same sampling instant.
|
||||
|
||||
#### Scenario: Calculate expected price at the configured start
|
||||
- **WHEN** the sampling instant is 2026-07-31T22:00:00Z
|
||||
@@ -26,7 +26,7 @@ Evelyn SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker obs
|
||||
- **THEN** the respective index is 0, 1, -1, 10, or -5
|
||||
|
||||
### Requirement: Evelyn instances have safe unique persistent names
|
||||
Every Evelyn instance SHALL have an NFC-normalized Unicode name whose preserved spelling and capitalization selects the direct child directory data/evelyn/<name>. A name SHALL be rejected if it is empty or whitespace-only, has leading or trailing whitespace, is . or .., ends with a period, contains a control character or any of < > : " / \ | ? *, is a case-insensitive Windows-reserved filename including a reserved base with an extension, or normalizes as a path to anything other than one direct child of data/evelyn. Within one JVM, normalized names SHALL be reserved atomically and compared case-insensitively using locale-independent rules.
|
||||
Every `EvelynStatusService` instance SHALL have an NFC-normalized Unicode name whose preserved spelling and capitalization selects the direct child directory data/evelyn/<name>. A name SHALL be rejected if it is empty or whitespace-only, has leading or trailing whitespace, is . or .., ends with a period, contains a control character or any of < > : " / \ | ? *, is a case-insensitive Windows-reserved filename including a reserved base with an extension, or normalizes as a path to anything other than one direct child of data/evelyn. Within one JVM, normalized names SHALL be reserved atomically and compared case-insensitively using locale-independent rules.
|
||||
|
||||
#### Scenario: A portable Unicode name is accepted
|
||||
- **WHEN** an instance is constructed with a valid Unicode name containing Danish characters
|
||||
@@ -41,10 +41,10 @@ Every Evelyn instance SHALL have an NFC-normalized Unicode name whose preserved
|
||||
- **THEN** exactly one reserves the persistent identity and the other fails
|
||||
|
||||
### Requirement: Evelyn distinguishes restartable stop from permanent close
|
||||
Stopping Evelyn SHALL terminate sampling, clear memory, preserve its persistent file, and retain its name reservation so the same object can restart. Evelyn SHALL support permanent, idempotent close; closing an active instance SHALL first stop it safely, a successfully closed instance SHALL never start again, and its name SHALL be released only after its sampling thread has definitely terminated. A termination failure SHALL be reported and SHALL retain the reservation.
|
||||
Stopping `EvelynStatusService` SHALL terminate sampling, clear memory, preserve its persistent file, and retain its name reservation so the same object can restart. `EvelynStatusService` SHALL support permanent, idempotent close; closing an active instance SHALL first stop it safely, a successfully closed instance SHALL never start again, and its name SHALL be released only after its sampling thread has definitely terminated. A termination failure SHALL be reported and SHALL retain the reservation.
|
||||
|
||||
#### Scenario: A stopped instance restarts
|
||||
- **WHEN** a stopped but not closed Evelyn instance is started again
|
||||
- **WHEN** a stopped but not closed `EvelynStatusService` instance is started again
|
||||
- **THEN** it retains exclusive ownership of the same name and reloads the complete persisted history before sampling
|
||||
|
||||
#### Scenario: An active instance closes successfully
|
||||
@@ -60,7 +60,7 @@ Stopping Evelyn SHALL terminate sampling, clear memory, preserve its persistent
|
||||
- **THEN** it completes without changing state or failing
|
||||
|
||||
### Requirement: Evelyn uses externally provisioned per-instance storage
|
||||
Each named instance SHALL use the existing regular file data/evelyn/<normalized-name>/status.log. Before every start, Evelyn SHALL require the direct instance path to be an existing readable and writable directory and status.log to be an existing readable and writable regular file. Evelyn SHALL NOT create, replace, repair, truncate, rename, or otherwise provision either path. Any validation or actual open failure SHALL identify the affected path, leave memory unpublished, and prevent sampling from starting.
|
||||
Each named instance SHALL use the existing regular file data/evelyn/<normalized-name>/status.log. Before every start, `EvelynStatusService` SHALL require the direct instance path to be an existing readable and writable directory and status.log to be an existing readable and writable regular file. `EvelynStatusService` SHALL NOT create, replace, repair, truncate, rename, or otherwise provision either path. Any validation or actual open failure SHALL identify the affected path, leave memory unpublished, and prevent sampling from starting.
|
||||
|
||||
#### Scenario: Required storage is absent or unsuitable
|
||||
- **WHEN** the instance directory or status file is missing, the path has the wrong type, or required access is unavailable
|
||||
@@ -73,11 +73,11 @@ Each named instance SHALL use the existing regular file data/evelyn/<normalized-
|
||||
### Requirement: Evelyn validates status history format strictly
|
||||
The UTF-8 status.log format SHALL have supported version 1, declared by FORMAT_VERSION=1 as the first actual entry; blank lines and full-line comments MAY precede it when loading, while an externally provisioned new file SHALL place it on the physical first line. Missing, duplicate, malformed, misplaced, or unsupported declarations SHALL fail loading before record parsing. After the declaration, blank lines and comments SHALL be ignored, inline comments MAY follow data, and every other entry SHALL be a valid record with an explicit stable numeric type code.
|
||||
|
||||
Type 1 SHALL encode <UTC uuuuMMddHHmmssSSS'Z' timestamp>:1:<plain BigDecimal Evelyn Price Index>. It SHALL have exactly three fields. Unknown types, invalid timestamps or decimals, missing or surplus fields, and decreasing timestamps SHALL fail the complete load; equal timestamps SHALL be accepted. A valid declaration with no records SHALL represent empty history. Applicable failures SHALL identify the file path, one-based line number, and original line, and Evelyn SHALL neither skip, sort, partially publish, nor repair invalid content.
|
||||
Type 1 SHALL encode <UTC uuuuMMddHHmmssSSS'Z' timestamp>:1:<plain BigDecimal Evelyn Price Index>. It SHALL have exactly three fields. Unknown types, invalid timestamps or decimals, missing or surplus fields, and decreasing timestamps SHALL fail the complete load; equal timestamps SHALL be accepted. A valid declaration with no records SHALL represent empty history. Applicable failures SHALL identify the file path, one-based line number, and original line, and `EvelynStatusService` SHALL neither skip, sort, partially publish, nor repair invalid content.
|
||||
|
||||
#### Scenario: A valid versioned history loads
|
||||
- **WHEN** the file has a valid first actual version declaration and valid type-1 records in non-decreasing timestamp order
|
||||
- **THEN** Evelyn restores every record in file order with zero values for the five unimplemented indexes
|
||||
- **THEN** the status service restores every record in file order with zero values for the five unimplemented indexes
|
||||
|
||||
#### Scenario: The format declaration is invalid
|
||||
- **WHEN** the declaration is missing, duplicated, malformed, misplaced, or not version 1
|
||||
@@ -88,7 +88,7 @@ Type 1 SHALL encode <UTC uuuuMMddHHmmssSSS'Z' timestamp>:1:<plain BigDecimal Eve
|
||||
- **THEN** startup fails with path, line number, and original line without exposing partial history or altering the file
|
||||
|
||||
### Requirement: Evelyn samples without overlapping or catch-up attempts
|
||||
Each started Evelyn environment SHALL own its sampling executor, attempt its first sample only after complete storage validation and history restoration, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the Evelyn lifecycle independently of whether Mission Control is open. Evelyn SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup or persistence operation without adding a fake or unpersisted point, terminating Evelyn, or cancelling the next attempt. Stopping Evelyn SHALL terminate its sampling executor.
|
||||
Each started `EvelynStatusService` instance SHALL own its sampling executor, attempt its first sample only after complete storage validation and history restoration, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the status-service lifecycle independently of whether Mission Control is open. `EvelynStatusService` SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup or persistence operation without adding a fake or unpersisted point, terminating the status service, or cancelling the next attempt. Stopping `EvelynStatusService` SHALL terminate its sampling executor.
|
||||
|
||||
#### Scenario: Initial Ticker data is unavailable
|
||||
- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price
|
||||
@@ -107,14 +107,14 @@ Each started Evelyn environment SHALL own its sampling executor, attempt its fir
|
||||
- **THEN** no record or point is added, the failure is logged, and later attempts remain scheduled
|
||||
|
||||
### Requirement: Evelyn provides historical status-index measurements
|
||||
Each named Evelyn instance SHALL publish a safe, non-null, oldest-to-newest snapshot only after its complete persisted history has been validated and loaded. Each type-1 point SHALL contain its persisted or sampled timestamp, the Evelyn Price Index, and BigDecimal.ZERO placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use independently named files, history collections, reservations, and lifecycles. A sampled point SHALL be durably appended using robust line-boundary handling before publication. Stop SHALL clear only memory; restart SHALL restore the complete file, and close SHALL preserve the file.
|
||||
Each named `EvelynStatusService` instance SHALL publish a safe, non-null, oldest-to-newest snapshot only after its complete persisted history has been validated and loaded. Each type-1 point SHALL contain its persisted or sampled timestamp, the Evelyn Price Index, and BigDecimal.ZERO placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use independently named files, history collections, reservations, and lifecycles. A sampled point SHALL be durably appended using robust line-boundary handling before publication. Stop SHALL clear only memory; restart SHALL restore the complete file, and close SHALL preserve the file.
|
||||
|
||||
#### Scenario: Status-index history is requested during sampling
|
||||
- **WHEN** a caller requests an Evelyn environment's status-index history while persisted points may be appended
|
||||
- **WHEN** a caller requests a status-service instance's status-index history while persisted points may be appended
|
||||
- **THEN** it receives a safe snapshot containing only completely persisted points and all six typed fields
|
||||
|
||||
#### Scenario: A process starts two named Evelyn environments
|
||||
- **WHEN** Production and Test Evelyn instances start with independently provisioned files
|
||||
- **WHEN** Production and Test `EvelynStatusService` instances start with independently provisioned files
|
||||
- **THEN** each restores and extends only its own complete history even if calculated values are identical
|
||||
|
||||
#### Scenario: Existing file lacks a final line separator
|
||||
@@ -126,20 +126,65 @@ Each named Evelyn instance SHALL publish a safe, non-null, oldest-to-newest snap
|
||||
- **THEN** its memory is cleared at stop and its complete unchanged persistent history is revalidated and restored before sampling resumes
|
||||
|
||||
### Requirement: Evelyn Mission Control visualizes status-index measurements
|
||||
Evelyn Mission Control SHALL display separate live Production and Test Overview charts exclusively from their corresponding authoritative Evelyn snapshots and SHALL never read or manage persistence directly. Each chart SHALL contain one enabled series named Evelyn Price Index; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete restored current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, close, persist, or clear either Evelyn environment.
|
||||
Evelyn Mission Control SHALL display separate live Production and Test Overview charts exclusively from their corresponding authoritative `EvelynStatusService` snapshots and SHALL never read or manage persistence directly. Each chart SHALL contain one enabled series named Evelyn Price Index; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete restored current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, close, persist, or clear either status-service instance.
|
||||
|
||||
#### Scenario: Production and Test Overviews show restored history
|
||||
- **WHEN** Mission Control opens after two named Evelyn environments have restored their histories
|
||||
- **WHEN** Mission Control opens after two named `EvelynStatusService` instances have restored their histories
|
||||
- **THEN** it displays separate one-series charts immediately populated from the corresponding complete snapshots
|
||||
|
||||
#### Scenario: A successful point appears while EMC is open
|
||||
- **WHEN** an Evelyn history publishes a newly persisted point
|
||||
- **WHEN** a status-service history publishes a newly persisted point
|
||||
- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC
|
||||
|
||||
#### Scenario: EMC reopens after collecting hidden-window samples
|
||||
- **WHEN** the same EMC instance is reopened after Evelyn persisted additional points while its window was hidden
|
||||
- **WHEN** the same EMC instance is reopened after the status services persisted additional points while its window was hidden
|
||||
- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state
|
||||
|
||||
#### Scenario: Future fields contain larger values
|
||||
- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index
|
||||
- **THEN** that disabled value does not affect the visible Y-axis bounds
|
||||
|
||||
### Requirement: Status service has an explicit public boundary
|
||||
The system SHALL expose `com.r35157.evelyn.service.status.EvelynStatusService` as the status-history and reference-price API. Its process IDs SHALL be `evelyn.status.prod` and `evelyn.status.test`. The service rename SHALL preserve the Production/Test storage identities, existing lifecycle operations, `EvelynStatusIndexPoint` and its six fields. Evelyn SHALL remain the overall system name, with NenjimHub owning individual process startup.
|
||||
|
||||
#### Scenario: Consumer uses the renamed status API
|
||||
- **WHEN** a consumer requests status history or a reference price
|
||||
- **THEN** it uses `EvelynStatusService` without accessing an implementation helper or a new service container
|
||||
|
||||
#### Scenario: Existing Production and Test storage survives the rename
|
||||
- **WHEN** the renamed services are constructed and started with the existing Production and Test identities
|
||||
- **THEN** they use the unchanged `data/evelyn/Production/status.log` and `data/evelyn/Test/status.log` files and format version 1 without migration or provisioning
|
||||
|
||||
### Requirement: Reference price is calculated on demand with an explicit quote currency
|
||||
`EvelynStatusService` SHALL expose `AssetPrice getReferencePrice(Instant timestamp)`. For each supplied instant on or after 2026-07-31T22:00:00Z it SHALL calculate 15.00 × 1.20^(elapsedSeconds / 31,536,000), returning the canonical EVE/USDT trading pair. It SHALL preserve the existing USDT-as-USD index convention without conversion, duplicate constants, separate configuration, cached historical measurements, or dependence on a sampling interval.
|
||||
|
||||
#### Scenario: Public reference price at the start
|
||||
- **WHEN** a consumer requests the reference price for 2026-07-31T22:00:00Z
|
||||
- **THEN** the price is 15 with the canonical EVE base and USDT quote currencies
|
||||
|
||||
#### Scenario: Public reference price after one year
|
||||
- **WHEN** a consumer requests the reference price exactly 31,536,000 seconds after the start
|
||||
- **THEN** the price is 18 with the same canonical trading pair
|
||||
|
||||
#### Scenario: Supplied timestamp determines the price
|
||||
- **WHEN** a consumer requests a valid instant distinct from the clock or latest historical point
|
||||
- **THEN** the returned value is calculated for that instant rather than the current clock or historical sample
|
||||
|
||||
### Requirement: Reference-price requests are independent of sampling and storage
|
||||
A reference-price request SHALL succeed before sampling starts and while sampling is stopped, including when history storage has not been provisioned. It SHALL NOT fetch market prices, access history files, start a worker, or mutate status history. A null timestamp SHALL be rejected with `NullPointerException`; an instant before the reference curve starts SHALL be rejected with `IllegalArgumentException`.
|
||||
|
||||
#### Scenario: Reference price before startup without storage
|
||||
- **WHEN** a constructed service with no provisioned history storage receives a valid reference-price request
|
||||
- **THEN** it returns the calculated value without a ticker lookup, file access, worker startup, or history change
|
||||
|
||||
#### Scenario: Reference price while sampling is stopped
|
||||
- **WHEN** sampling is stopped and a valid reference-price request is made
|
||||
- **THEN** the service returns the calculated value without restarting sampling or reading, writing, or mutating history
|
||||
|
||||
#### Scenario: Reference price during sampling preserves history
|
||||
- **WHEN** reference-price requests are made while the service holds a status-history snapshot
|
||||
- **THEN** the requests do not add or alter any status point or file record
|
||||
|
||||
#### Scenario: Null or pre-start timestamp is rejected
|
||||
- **WHEN** a caller supplies null or an instant before 2026-07-31T22:00:00Z
|
||||
- **THEN** the service rejects it with the respective null or argument exception without side effects
|
||||
|
||||
@@ -12,7 +12,7 @@ Provide one typed, read-only catalogue of constructed Nenjim components and a li
|
||||
- **THEN** its stored value is `nenjim.registry.service`
|
||||
|
||||
#### Scenario: Valid component IDs are accepted
|
||||
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.service.prod` are constructed
|
||||
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.status.prod` are constructed
|
||||
- **THEN** construction succeeds
|
||||
|
||||
#### Scenario: Invalid component IDs are rejected
|
||||
|
||||
@@ -85,7 +85,7 @@ After validating the entire plan, the Hub SHALL construct the configured Registr
|
||||
- **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 Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any process-specific passive administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||
|
||||
The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||
|
||||
@@ -109,6 +109,14 @@ The configuration SHALL be the complete manually ordered process dependency clos
|
||||
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||
|
||||
#### Scenario: Renamed status services retain dependency wiring
|
||||
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||
|
||||
#### Scenario: Deployment still contains an old status ID
|
||||
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.fanitas.evelyn.core;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Collects and exposes the status-index history for one named Evelyn instance.
|
||||
*/
|
||||
public interface Evelyn extends NenjimComponent, AutoCloseable {
|
||||
/**
|
||||
* Executes the legacy Evelyn service operation.
|
||||
*
|
||||
* @throws Exception when service execution fails
|
||||
*/
|
||||
void executeService() throws Exception;
|
||||
|
||||
/**
|
||||
* Restores persisted history and starts status-index sampling.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stops sampling and clears memory while retaining persistent ownership.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Permanently closes this instance and releases its persistent name.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Returns an immutable snapshot containing only durably persisted points.
|
||||
*
|
||||
* @return complete current history ordered from oldest to newest
|
||||
*/
|
||||
@NotNull List<EvelynStatusIndexPoint> getStatusIndexHistory();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.r35157.evelyn.emc.impl.ref;
|
||||
|
||||
import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.r35157.evelyn.service.status.EvelynStatusService;
|
||||
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.nenjim.process.NenjimProcess;
|
||||
@@ -33,12 +33,12 @@ public final class EvelynMissionControlImpl implements
|
||||
NenjimProcess {
|
||||
|
||||
public EvelynMissionControlImpl(
|
||||
@NotNull Evelyn evelynServiceProd,
|
||||
@NotNull Evelyn evelynServiceTest,
|
||||
@NotNull EvelynStatusService evelynStatusServiceProd,
|
||||
@NotNull EvelynStatusService evelynStatusServiceTest,
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService
|
||||
) {
|
||||
this.evelynProd = Objects.requireNonNull(evelynServiceProd, "evelynServiceProd");
|
||||
this.evelynTest = Objects.requireNonNull(evelynServiceTest, "evelynServiceTest");
|
||||
this.evelynStatusProd = Objects.requireNonNull(evelynStatusServiceProd, "evelynStatusServiceProd");
|
||||
this.evelynStatusTest = Objects.requireNonNull(evelynStatusServiceTest, "evelynStatusServiceTest");
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
@@ -253,8 +253,8 @@ public final class EvelynMissionControlImpl implements
|
||||
}
|
||||
|
||||
private void refreshCharts() {
|
||||
refreshChart(productionChart, evelynProd.getStatusIndexHistory());
|
||||
refreshChart(testChart, evelynTest.getStatusIndexHistory());
|
||||
refreshChart(productionChart, evelynStatusProd.getStatusIndexHistory());
|
||||
refreshChart(testChart, evelynStatusTest.getStatusIndexHistory());
|
||||
}
|
||||
|
||||
static final class StatusIndexChart {
|
||||
@@ -313,8 +313,8 @@ public final class EvelynMissionControlImpl implements
|
||||
}
|
||||
};
|
||||
|
||||
private final Evelyn evelynProd;
|
||||
private final Evelyn evelynTest;
|
||||
private final EvelynStatusService evelynStatusProd;
|
||||
private final EvelynStatusService evelynStatusTest;
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
private final Timeline refreshTimeline = new Timeline(new KeyFrame(
|
||||
Duration.seconds(CHART_REFRESH_SECONDS),
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.r35157.nenjim.hubd.impl.ref;
|
||||
import com.r35157.assetaz.services.journal.impl.ref.AssetAZJournalServiceImpl;
|
||||
import com.r35157.assetaz.services.journal.codec.impl.ref.AssetOwnerRegisteredCodec;
|
||||
import com.r35157.assetaz.services.journal.codec.impl.ref.JournalFormatVersionChangedCodec;
|
||||
import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
|
||||
import com.r35157.evelyn.service.status.EvelynStatusService;
|
||||
import com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl;
|
||||
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
||||
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -274,29 +274,29 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
return process;
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynProdProcess() {
|
||||
EvelynImpl process = new EvelynImpl(
|
||||
private NenjimProcess constructEvelynStatusProdProcess() {
|
||||
EvelynStatusServiceImpl process = new EvelynStatusServiceImpl(
|
||||
"Production",
|
||||
requireProcessDependency(
|
||||
tickerService,
|
||||
EVELYN_SERVICE_PROD_ID,
|
||||
EVELYN_STATUS_PROD_ID,
|
||||
TICKER_SERVICE_ID),
|
||||
eveUSDT,
|
||||
clockUTC);
|
||||
evelynServiceProd = process;
|
||||
evelynStatusServiceProd = process;
|
||||
return process;
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynTestProcess() {
|
||||
EvelynImpl process = new EvelynImpl(
|
||||
private NenjimProcess constructEvelynStatusTestProcess() {
|
||||
EvelynStatusServiceImpl process = new EvelynStatusServiceImpl(
|
||||
"Test",
|
||||
requireProcessDependency(
|
||||
tickerService,
|
||||
EVELYN_SERVICE_TEST_ID,
|
||||
EVELYN_STATUS_TEST_ID,
|
||||
TICKER_SERVICE_ID),
|
||||
eveUSDT,
|
||||
clockUTC);
|
||||
evelynServiceTest = process;
|
||||
evelynStatusServiceTest = process;
|
||||
return process;
|
||||
}
|
||||
|
||||
@@ -335,13 +335,13 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
private NenjimProcess constructMissionControlProcess() {
|
||||
EvelynMissionControlImpl process = new EvelynMissionControlImpl(
|
||||
requireProcessDependency(
|
||||
evelynServiceProd,
|
||||
evelynStatusServiceProd,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
EVELYN_SERVICE_PROD_ID),
|
||||
EVELYN_STATUS_PROD_ID),
|
||||
requireProcessDependency(
|
||||
evelynServiceTest,
|
||||
evelynStatusServiceTest,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
EVELYN_SERVICE_TEST_ID),
|
||||
EVELYN_STATUS_TEST_ID),
|
||||
requireProcessDependency(
|
||||
javaFxRuntimeService,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
@@ -385,8 +385,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
registryService, Path.of("data/assetaz/journal/events.pde"))),
|
||||
Map.entry(TICKER_SERVICE_ID, this::constructTickerProcess),
|
||||
Map.entry(JUPITER_PERPS_ALARM_ID, this::constructJupiterPerpsAlarmProcess),
|
||||
Map.entry(EVELYN_SERVICE_PROD_ID, this::constructEvelynProdProcess),
|
||||
Map.entry(EVELYN_SERVICE_TEST_ID, this::constructEvelynTestProcess),
|
||||
Map.entry(EVELYN_STATUS_PROD_ID, this::constructEvelynStatusProdProcess),
|
||||
Map.entry(EVELYN_STATUS_TEST_ID, this::constructEvelynStatusTestProcess),
|
||||
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),
|
||||
@@ -491,10 +491,10 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
new NenjimComponentId("jupiter.perps.evelyn-test");
|
||||
private static final NenjimComponentId JUPITER_PERPS_ALARM_ID =
|
||||
new NenjimComponentId("jupiter-perps-alarm.default");
|
||||
private static final NenjimComponentId EVELYN_SERVICE_PROD_ID =
|
||||
new NenjimComponentId("evelyn.service.prod");
|
||||
private static final NenjimComponentId EVELYN_SERVICE_TEST_ID =
|
||||
new NenjimComponentId("evelyn.service.test");
|
||||
private static final NenjimComponentId EVELYN_STATUS_PROD_ID =
|
||||
new NenjimComponentId("evelyn.status.prod");
|
||||
private static final NenjimComponentId EVELYN_STATUS_TEST_ID =
|
||||
new NenjimComponentId("evelyn.status.test");
|
||||
private static final NenjimComponentId JUPITER_SWAP_EVELYN_BURNER_PROD_ID =
|
||||
new NenjimComponentId("jupiter.swap.evelyn-burner-prod");
|
||||
private static final NenjimComponentId DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID =
|
||||
@@ -539,8 +539,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
// Services
|
||||
private TickerService tickerService;
|
||||
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
||||
private Evelyn evelynServiceProd;
|
||||
private Evelyn evelynServiceTest;
|
||||
private EvelynStatusService evelynStatusServiceProd;
|
||||
private EvelynStatusService evelynStatusServiceTest;
|
||||
private JupiterSwapService jupiterSwapService;
|
||||
private BoundNotificationService discordAssetAZTokenNotificationService;
|
||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||
|
||||
Reference in New Issue
Block a user