97: Configure separate TEST and PROD instances of EvelynPerpsService
This commit is contained in:
+8
-3
@@ -440,12 +440,17 @@ positioner med stabile ID'er blandt søskende, adskilt fra visningsnavne. Positi
|
||||
`LONG`/`SHORT` og decimal USD-størrelse. Svaret indeholder ingen JavaFX-elementer, farver eller formatering.
|
||||
|
||||
`impl.ref.EvelynPerpsServiceImpl` implementerer desuden `NenjimProcess`. Hub'en registrerer og starter det samme
|
||||
objekt som `evelyn.perps.default` før EMC. Før `start()` afvises læsning; gentagen start afvises også.
|
||||
objekt pr. miljø som `evelyn.perps.prod` og `evelyn.perps.test` før EMC.
|
||||
Hub'en definerer den fælles hardcodede liste af `PerpsWalletConfiguration` (ID og visningsnavn) og giver den
|
||||
til begge konstruktører. Hver instans kopierer konfigurationen og ejer sit eget snapshot. Eksempel-ID'erne er
|
||||
ikke Solana-adresser. Servicen fordeler de to eksisterende eksempelporteføljer skiftevis efter wallet-rækkefølge;
|
||||
positionerne for de to konfigurerede wallets er uændrede. Før `start()` afvises læsning; gentagen start afvises også.
|
||||
Service-API'et eksponerer kun læsning, ingen lifecycle- eller positionsændringer. Servicen ejer de otte
|
||||
uændrede eksempelpositioner fra den oprindelige Perps-fane og har ingen workers eller Jupiter/Phoenix-kald.
|
||||
|
||||
EMC læser snapshot'et straks ved åbning og hvert tredje sekund på en separat worker, uafhængigt af en eventuel
|
||||
senere integrations opdateringsinterval. Begge miljøfaner viser fortsat de samme tydeligt markerede eksempler;
|
||||
EMC læser hvert miljøs snapshot fra den tilsvarende service straks ved åbning og hvert tredje sekund på en
|
||||
separat worker, uafhængigt af en eventuel senere integrations opdateringsinterval. Fejl i ét servicekald forhindrer ikke forsøget på det andet.
|
||||
Begge miljøfaner viser fortsat de samme tydeligt markerede eksempler;
|
||||
EMC ejer træstruktur, USD-formatering og grønne/røde rækker. Uændrede snapshots berører ikke eksisterende rækker,
|
||||
foldestatus eller markering. Ved ændringer genbruges træelementer efter deres ID'er inden for samme forælder.
|
||||
UI-opdateringer sker kun på JavaFX-tråden. Fejl bevarer visningen og stopper ikke næste pollingforsøg.
|
||||
|
||||
+11
-7
@@ -271,7 +271,8 @@ jupiter-perps-alarm.default
|
||||
evelyn.status.prod
|
||||
evelyn.status.test
|
||||
evelyn.iou-burner.prod
|
||||
evelyn.perps.default
|
||||
evelyn.perps.prod
|
||||
evelyn.perps.test
|
||||
nenjim.javafx-runtime.service
|
||||
evelyn.mission-control.default
|
||||
nenjim.process-manager.default
|
||||
@@ -284,12 +285,15 @@ navngivne `Production`- og `Test`-instanser gennem `EvelynStatusServiceImpl` og
|
||||
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
||||
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
||||
|
||||
Mission Control kræver også `evelyn.perps.default` før `evelyn.mission-control.default` i den private
|
||||
`conf/NenjimHub.conf`. Hub'en konstruerer, registrerer og starter `EvelynPerpsServiceImpl` som en separat
|
||||
proces og injicerer samme objekts read-only `EvelynPerpsService`-view i EMC. Process Manager viser ID'et efter
|
||||
vellykket startup. Den lokale udviklingskonfiguration vælger nu servicen før EMC; andre deployments med EMC
|
||||
skal tilføje samme linje. Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk
|
||||
indsættelse. Servicen bruger kun uændrede in-memory-eksempler og foretager ingen eksterne positionskald.
|
||||
Mission Control kræver både `evelyn.perps.prod` og `evelyn.perps.test` før `evelyn.mission-control.default`
|
||||
i den private `conf/NenjimHub.conf`. Erstat den tidligere `evelyn.perps.default`-linje med de to nye ID'er;
|
||||
det gamle ID accepteres ikke længere. Hub'en konstruerer, registrerer og starter to separate
|
||||
`EvelynPerpsServiceImpl`-processer med samme hardcodede wallet-konfiguration og injicerer deres read-only
|
||||
service-views til de tilsvarende miljøfaner i EMC. Process Manager viser begge ID'er efter vellykket startup.
|
||||
Den lokale udviklingskonfiguration er opdateret; andre deployments med EMC skal migreres tilsvarende.
|
||||
Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk indsættelse.
|
||||
Det minimale versionsstyrede bootstrap-eksempel er uændret. Hver service ejer sit eget snapshot med
|
||||
uændrede in-memory-eksempler og foretager ingen eksterne positionskald.
|
||||
|
||||
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
|
||||
`evelyn.status.prod` stå før den. Hub'en injicerer Production status-servicen til
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-22
|
||||
@@ -0,0 +1,22 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
See proposal.md. The service currently creates two named sample wallets internally; EMC uses one shared service and snapshot for both tabs. Hub startup is explicitly ordered.
|
||||
|
||||
## Goals / Non-Goals
|
||||
Keep wallet configuration out of EMC and snapshots instance-owned. No real addresses, integrations, trading, new configuration framework or UI redesign are introduced.
|
||||
|
||||
## Decisions
|
||||
- Add an immutable wallet configuration record (stable ID and display name), supplied as a defensively copied list to the implementation constructor. The sample IDs are not Solana addresses. This avoids passing UI data or moving sample position definitions into Hub.
|
||||
- Hub owns one immutable list of the two existing sample wallet identities, supplied to both independently constructed processes.
|
||||
- Keep the existing two sample position sets in the service and assign them by configured wallet order, alternating for additional sample wallets. This is explicitly sample-only behavior, not an integration policy; empty configuration gives an empty snapshot. It avoids coupling sample data to wallet IDs or requiring exactly two wallets.
|
||||
- Inject PROD and TEST service views into EMC. Retain one off-thread scheduled loop, with separate reads and error handling for each tab. Preserve existing rendering and session guards.
|
||||
- Retire the default ID rather than aliasing it, following explicit process selection and fail-fast dependencies.
|
||||
|
||||
## Risks / Trade-offs
|
||||
- Identical fixtures conceal crossed wiring → verify routing by source inspection and an isolated session with distinct snapshots.
|
||||
- Private startup configuration is ignored by Git → update the local file and document the required deployment migration.
|
||||
- Sample assignment is ordinal → document this temporary fixture behavior; real integration remains a later task.
|
||||
|
||||
## Migration Plan
|
||||
Replace evelyn.perps.default with evelyn.perps.prod and evelyn.perps.test before EMC. Both must precede EMC. The minimal tracked bootstrap template stays unchanged. Rollback requires restoring both code and the old configuration entry. Do not start production processes during verification.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
EMC currently shares one Perps service between TEST and PROD. Issue #97 requires separate service ownership and Hub-supplied wallet configuration before real integrations are introduced.
|
||||
|
||||
## What Changes
|
||||
- **BREAKING**: Replace `evelyn.perps.default` with `evelyn.perps.test` and `evelyn.perps.prod` in the process catalogue and startup configuration.
|
||||
- Pass the same hardcoded wallet configuration from NenjimHub to two independent service instances.
|
||||
- Poll each instance for its corresponding EMC tab every three seconds, preserving presentation and the parameterless snapshot API.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
None.
|
||||
|
||||
### Modified Capabilities
|
||||
- `evelyn-perps-service`: Hub-configured wallets and separate TEST/PROD lifecycles and snapshots.
|
||||
- `nenjim-hub-configuration`: Separate Perps process IDs and explicit EMC dependencies.
|
||||
- `evelyn-perps-overview`: Each environment tab polls its own service.
|
||||
|
||||
## Impact
|
||||
Perps service construction, EMC injection and polling, Hub process catalogue, local startup configuration and project documentation. No exchange integration, trading, advanced configuration or UI changes.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Asynchronous polling follows window visibility
|
||||
EMC SHALL request the current Perps snapshot from each environment's own service immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||
|
||||
#### Scenario: Read takes time or fails
|
||||
- **WHEN** a snapshot read is delayed or fails
|
||||
- **THEN** the UI remains responsive and the existing view is retained
|
||||
- **AND** later polling remains possible without overlapping scheduled reads
|
||||
|
||||
#### Scenario: Hide and reopen
|
||||
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||
|
||||
#### Scenario: Environment data stays separate
|
||||
- **WHEN** TEST and PROD return different snapshots
|
||||
- **THEN** each tab displays only its own service's data
|
||||
- **AND** a failure reading one service does not prevent attempting the other service
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Hub owns Perps startup
|
||||
The service SHALL run as two separate Hub-managed processes registered as `evelyn.perps.test` and `evelyn.perps.prod`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||
|
||||
#### Scenario: Service starts
|
||||
- **WHEN** the Hub starts either registered Perps process
|
||||
- **THEN** its service view makes the complete sample snapshot available without starting a polling or integration worker
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Wallet configuration belongs to service construction
|
||||
The Hub SHALL supply the same hardcoded wallet configuration to TEST and PROD at construction. Each service SHALL retain an immutable copy and own a separate snapshot. EMC SHALL neither own nor pass wallet configuration; snapshot reads SHALL remain parameterless.
|
||||
|
||||
#### Scenario: Separate configured instances
|
||||
- **WHEN** the Hub constructs and starts both instances using the shared sample wallet configuration
|
||||
- **THEN** each snapshot contains the configured wallet identities and existing sample positions
|
||||
- **AND** the two instances own separate snapshots without defining the wallet list internally
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.prod` and `evelyn.perps.test`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||
|
||||
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.prod` and `evelyn.perps.test` services, both of which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||
|
||||
#### Scenario: Configured process starts successfully
|
||||
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||
|
||||
#### Scenario: Process is omitted
|
||||
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||
|
||||
#### Scenario: Jupiter Perps Alarm is selected
|
||||
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||
|
||||
#### Scenario: Dependency order is invalid
|
||||
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||
|
||||
#### Scenario: JavaFX processes are omitted
|
||||
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||
|
||||
#### Scenario: Renamed status services retain dependency wiring
|
||||
- **WHEN** `evelyn.status.prod` and `evelyn.status.test` are listed after Ticker and before Mission Control
|
||||
- **THEN** the Hub constructs separate Production/Test status services, registers the renamed IDs, and injects the corresponding typed service views into Mission Control in the configured order
|
||||
|
||||
#### Scenario: Deployment still contains an old status ID
|
||||
- **WHEN** a private deployment configuration contains `evelyn.service.prod` or `evelyn.service.test`
|
||||
- **THEN** validation rejects the unknown ID before startup; deployment migration replaces only that ID with `evelyn.status.prod` or `evelyn.status.test`, preserving selection and order
|
||||
|
||||
#### Scenario: Liquidity is explicitly selected
|
||||
- **WHEN** `evelyn.liquidity.prod` is reached in a valid startup plan
|
||||
- **THEN** the Hub loads its runtime configuration, binds the dedicated signer/wallet and Jupiter service to each other, constructs direct Raydium access and registers/starts the liquidity process without inserting a Ticker or status dependency or reordering the plan
|
||||
|
||||
#### Scenario: Production burner has its reference-price dependency
|
||||
- **WHEN** `evelyn.iou-burner.prod` is reached after Ticker and `evelyn.status.prod`
|
||||
- **THEN** the Hub injects the corresponding Production status service for buyback price decisions
|
||||
|
||||
#### Scenario: Production burner precedes its status dependency
|
||||
- **WHEN** the burner is reached without a previously constructed Production status service
|
||||
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||
|
||||
#### Scenario: EMC has its Perps dependency
|
||||
- **WHEN** `evelyn.perps.prod` and `evelyn.perps.test` appear before EMC in the deployment's ordered autostart list
|
||||
- **THEN** the Hub registers and starts separate service instances and injects their corresponding service views into EMC
|
||||
|
||||
#### Scenario: EMC precedes Perps
|
||||
- **WHEN** EMC is selected without both earlier Perps processes
|
||||
- **THEN** construction fails clearly without inserting or starting that dependency
|
||||
|
||||
#### Scenario: Deployment contains the retired shared Perps ID
|
||||
- **WHEN** a deployment still selects `evelyn.perps.default`
|
||||
- **THEN** startup validation rejects the unknown ID before starting processes
|
||||
- **AND** migration replaces it with both new IDs before EMC
|
||||
@@ -0,0 +1,19 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Service configuration and Hub ownership
|
||||
- [x] 1.1 Add immutable wallet configuration and separate TEST/PROD construction; inspect retained sample values, defensive copying and lifecycle wiring.
|
||||
- [x] 1.2 Update local startup configuration and service/startup documentation; verify both IDs precede EMC and the minimal tracked template is unchanged.
|
||||
|
||||
## 2. EMC routing
|
||||
- [x] 2.1 Inject both services and route independent reads to their respective tabs; verify three-second polling, error isolation and unchanged state-preserving renderer.
|
||||
|
||||
## 3. Integration verification
|
||||
- [x] 3.1 Run Gradle build, strict OpenSpec validation and isolated verification of service snapshots and distinct environment routing; review the diff and leave changes uncommitted.
|
||||
|
||||
## Verification results
|
||||
- Gradle build passed, including all 42 existing tests. No automated tests added to the repository.
|
||||
- Strict OpenSpec validation and `git diff --check HEAD` passed.
|
||||
- Isolated JavaFX verification used distinct PROD/TEST fixtures to verify routing, independent error handling, approximately 3.000-second poll gaps, unchanged row identities/selection/expansion, hide/reopen cancellation and obsolete-result rejection.
|
||||
- The actual local startup configuration parsed successfully; both Perps instances were constructed, registered and started through the Hub lifecycle in isolation, with separate but equal snapshots. No production Hub startup or exchange requests.
|
||||
- Local ignored `conf/NenjimHub.conf` now selects both IDs before EMC; tracked minimal bootstrap template is unchanged.
|
||||
- Changes left uncommitted for owner review; OpenSpec change remains active.
|
||||
@@ -37,7 +37,7 @@ The Perps view SHALL explicitly label its data as sample data and explain that P
|
||||
- **AND** displaying and interacting with the prototype uses only the service's local sample snapshot
|
||||
|
||||
### Requirement: Asynchronous polling follows window visibility
|
||||
EMC SHALL request the current Perps snapshot immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||
EMC SHALL request the current Perps snapshot from each environment's own service immediately on opening and every three seconds while visible, without blocking the UI thread. UI updates SHALL occur only on the JavaFX thread. Hiding the window SHALL stop polling; reopening SHALL create only one active polling loop and ignore results belonging to an earlier window session. Poll failures SHALL preserve the current view and allow subsequent attempts.
|
||||
|
||||
#### Scenario: Read takes time or fails
|
||||
- **WHEN** a snapshot read is delayed or fails
|
||||
@@ -48,6 +48,11 @@ EMC SHALL request the current Perps snapshot immediately on opening and every th
|
||||
- **WHEN** the owner hides and reopens EMC repeatedly
|
||||
- **THEN** each visible session has only one polling loop and obsolete results cannot update its views
|
||||
|
||||
#### Scenario: Environment data stays separate
|
||||
- **WHEN** TEST and PROD return different snapshots
|
||||
- **THEN** each tab displays only its own service's data
|
||||
- **AND** a failure reading one service does not prevent attempting the other service
|
||||
|
||||
### Requirement: Polling preserves Perps view state
|
||||
The Perps table SHALL retain matching wallet, venue and position identities across snapshots. Repeated unchanged snapshots SHALL leave existing rows, expansion, selection and formatting untouched without flicker. New groups SHALL be expanded by default, and future added or removed positions SHALL be reconciled by identity without resetting retained groups.
|
||||
|
||||
|
||||
@@ -22,8 +22,16 @@ The service SHALL own the existing eight sample positions grouped under Sample w
|
||||
- **AND** wallet B contains Jupiter ETH Long 1875.25 and SOL Short 950.00, Phoenix BTC Long 21000.00 and SOL Short 640.50
|
||||
|
||||
### Requirement: Hub owns Perps startup
|
||||
The service SHALL run as a separate Hub-managed process registered as `evelyn.perps.default`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||
The service SHALL run as two separate Hub-managed processes registered as `evelyn.perps.test` and `evelyn.perps.prod`. The service API SHALL expose no lifecycle operations. Snapshot reads before process startup SHALL fail clearly.
|
||||
|
||||
#### Scenario: Service starts
|
||||
- **WHEN** the Hub starts the registered Perps process
|
||||
- **WHEN** the Hub starts either registered Perps process
|
||||
- **THEN** its service view makes the complete sample snapshot available without starting a polling or integration worker
|
||||
|
||||
### Requirement: Wallet configuration belongs to service construction
|
||||
The Hub SHALL supply the same hardcoded wallet configuration to TEST and PROD at construction. Each service SHALL retain an immutable copy and own a separate snapshot. EMC SHALL neither own nor pass wallet configuration; snapshot reads SHALL remain parameterless.
|
||||
|
||||
#### Scenario: Separate configured instances
|
||||
- **WHEN** the Hub constructs and starts both instances using the shared sample wallet configuration
|
||||
- **THEN** each snapshot contains the configured wallet identities and existing sample positions
|
||||
- **AND** the two instances own separate snapshots without defining the wallet list internally
|
||||
|
||||
@@ -89,9 +89,9 @@ After validating the entire plan, the Hub SHALL construct the configured Registr
|
||||
- **THEN** its deployment configuration, dedicated signer/wallet, Jupiter service and direct Raydium service are neither loaded nor constructed nor registered
|
||||
|
||||
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.default`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.status.prod`, `evelyn.status.test`, `evelyn.iou-burner.prod`, `evelyn.liquidity.prod`, `evelyn.perps.prod` and `evelyn.perps.test`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any explicitly composed process-specific passive dependencies or administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||
|
||||
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.default` service, which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||
The production IOU burner SHALL receive the already constructed Production Evelyn status service, and SHALL require both Ticker and `evelyn.status.prod` to precede `evelyn.iou-burner.prod`. EMC SHALL receive the already started `evelyn.perps.prod` and `evelyn.perps.test` services, both of which SHALL precede EMC in the deployment autostart list. The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||
|
||||
#### Scenario: Configured process starts successfully
|
||||
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||
@@ -134,13 +134,18 @@ The production IOU burner SHALL receive the already constructed Production Evely
|
||||
- **THEN** construction fails clearly without creating or starting the omitted dependency or the burner
|
||||
|
||||
#### Scenario: EMC has its Perps dependency
|
||||
- **WHEN** `evelyn.perps.default` appears before EMC in the deployment's ordered autostart list
|
||||
- **THEN** the Hub registers and starts that service and injects its service view into EMC
|
||||
- **WHEN** `evelyn.perps.prod` and `evelyn.perps.test` appear before EMC in the deployment's ordered autostart list
|
||||
- **THEN** the Hub registers and starts separate service instances and injects their corresponding service views into EMC
|
||||
|
||||
#### Scenario: EMC precedes Perps
|
||||
- **WHEN** EMC is selected without an earlier Perps process
|
||||
- **WHEN** EMC is selected without both earlier Perps processes
|
||||
- **THEN** construction fails clearly without inserting or starting that dependency
|
||||
|
||||
#### Scenario: Deployment contains the retired shared Perps ID
|
||||
- **WHEN** a deployment still selects `evelyn.perps.default`
|
||||
- **THEN** startup validation rejects the unknown ID before starting processes
|
||||
- **AND** migration replaces it with both new IDs before EMC
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -41,12 +41,14 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
||||
public EvelynMissionControlImpl(
|
||||
@NotNull EvelynStatusService evelynStatusServiceProd,
|
||||
@NotNull EvelynStatusService evelynStatusServiceTest,
|
||||
@NotNull EvelynPerpsService evelynPerpsService,
|
||||
@NotNull EvelynPerpsService evelynPerpsServiceProd,
|
||||
@NotNull EvelynPerpsService evelynPerpsServiceTest,
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService
|
||||
) {
|
||||
this.evelynStatusProd = Objects.requireNonNull(evelynStatusServiceProd, "evelynStatusServiceProd");
|
||||
this.evelynStatusTest = Objects.requireNonNull(evelynStatusServiceTest, "evelynStatusServiceTest");
|
||||
this.evelynPerpsService = Objects.requireNonNull(evelynPerpsService, "evelynPerpsService");
|
||||
this.evelynPerpsServiceProd = Objects.requireNonNull(evelynPerpsServiceProd, "evelynPerpsServiceProd");
|
||||
this.evelynPerpsServiceTest = Objects.requireNonNull(evelynPerpsServiceTest, "evelynPerpsServiceTest");
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
@@ -288,20 +290,28 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
||||
});
|
||||
perpsPolling = session;
|
||||
session.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
PerpsSnapshot snapshot = evelynPerpsService.getPositionsSnapshot();
|
||||
javaFxRuntimeService.runLater(() -> {
|
||||
if (perpsPolling == session && window.isShowing()) {
|
||||
productionPerps.update(snapshot);
|
||||
testPerps.update(snapshot);
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("Unable to refresh Perps positions", exception);
|
||||
}
|
||||
pollPerps(session, evelynPerpsServiceProd, productionPerps, "PROD");
|
||||
pollPerps(session, evelynPerpsServiceTest, testPerps, "TEST");
|
||||
}, 0, PERPS_REFRESH_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void pollPerps(ScheduledExecutorService session, EvelynPerpsService service,
|
||||
PerpsTab tab, String environment) {
|
||||
if (perpsPolling != session) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
PerpsSnapshot snapshot = service.getPositionsSnapshot();
|
||||
javaFxRuntimeService.runLater(() -> {
|
||||
if (perpsPolling == session && window.isShowing()) {
|
||||
tab.update(snapshot);
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("Unable to refresh {} Perps positions", environment, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPerpsPolling() {
|
||||
ScheduledExecutorService session = perpsPolling;
|
||||
perpsPolling = null;
|
||||
@@ -382,7 +392,8 @@ public final class EvelynMissionControlImpl implements EvelynMissionControl, Nen
|
||||
// region Dependencies
|
||||
private final EvelynStatusService evelynStatusProd;
|
||||
private final EvelynStatusService evelynStatusTest;
|
||||
private final EvelynPerpsService evelynPerpsService;
|
||||
private final EvelynPerpsService evelynPerpsServiceProd;
|
||||
private final EvelynPerpsService evelynPerpsServiceTest;
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
// endregion
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.r35157.evelyn.service.perps;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** Hub-supplied wallet identity; sample IDs are not real Solana addresses. */
|
||||
public record PerpsWalletConfiguration(String id, String name) {
|
||||
public PerpsWalletConfiguration {
|
||||
Objects.requireNonNull(id, "id");
|
||||
Objects.requireNonNull(name, "name");
|
||||
}
|
||||
}
|
||||
+40
-16
@@ -2,6 +2,7 @@ package com.r35157.evelyn.service.perps.impl.ref;
|
||||
|
||||
import com.r35157.evelyn.service.perps.EvelynPerpsService;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot;
|
||||
import com.r35157.evelyn.service.perps.PerpsWalletConfiguration;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Position;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Venue;
|
||||
import com.r35157.evelyn.service.perps.PerpsSnapshot.Wallet;
|
||||
@@ -10,31 +11,53 @@ import com.r35157.nenjim.process.NenjimProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/** Owns the fixed examples only. No exchange clients, position management or update worker. */
|
||||
public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimProcess {
|
||||
public EvelynPerpsServiceImpl(@NotNull List<PerpsWalletConfiguration> wallets) {
|
||||
this.wallets = List.copyOf(wallets);
|
||||
var ids = new HashSet<String>();
|
||||
for (PerpsWalletConfiguration wallet : this.wallets) {
|
||||
if (!ids.add(wallet.id())) {
|
||||
throw new IllegalArgumentException("Duplicate Perps wallet ID: " + wallet.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
if (snapshot != null) {
|
||||
throw new IllegalStateException("Perps service has already started");
|
||||
}
|
||||
snapshot = new PerpsSnapshot(List.of(
|
||||
new Wallet("sample-wallet-a", "Sample wallet A", List.of(
|
||||
new Venue("jupiter", "Jupiter", List.of(
|
||||
position("sol-long", "SOL-PERP", Direction.LONG, "12500.00"),
|
||||
position("btc-short", "BTC-PERP", Direction.SHORT, "8250.50"))),
|
||||
new Venue("phoenix", "Phoenix", List.of(
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "3200.75"),
|
||||
position("eth-long", "ETH-PERP", Direction.LONG, "5400.00"))))),
|
||||
new Wallet("sample-wallet-b", "Sample wallet B", List.of(
|
||||
new Venue("jupiter", "Jupiter", List.of(
|
||||
position("eth-long", "ETH-PERP", Direction.LONG, "1875.25"),
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "950.00"))),
|
||||
new Venue("phoenix", "Phoenix", List.of(
|
||||
position("btc-long", "BTC-PERP", Direction.LONG, "21000.00"),
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "640.50")))))
|
||||
));
|
||||
var configuredWallets = new ArrayList<Wallet>();
|
||||
for (int index = 0; index < wallets.size(); index++) {
|
||||
PerpsWalletConfiguration wallet = wallets.get(index);
|
||||
configuredWallets.add(new Wallet(wallet.id(), wallet.name(), sampleVenues(index)));
|
||||
}
|
||||
snapshot = new PerpsSnapshot(configuredWallets);
|
||||
}
|
||||
|
||||
/** Alternate the original two sample portfolios in configured wallet order. */
|
||||
private static List<Venue> sampleVenues(int walletIndex) {
|
||||
if (walletIndex % 2 == 0) {
|
||||
return List.of(
|
||||
new Venue("jupiter", "Jupiter", List.of(
|
||||
position("sol-long", "SOL-PERP", Direction.LONG, "12500.00"),
|
||||
position("btc-short", "BTC-PERP", Direction.SHORT, "8250.50"))),
|
||||
new Venue("phoenix", "Phoenix", List.of(
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "3200.75"),
|
||||
position("eth-long", "ETH-PERP", Direction.LONG, "5400.00"))));
|
||||
}
|
||||
return List.of(
|
||||
new Venue("jupiter", "Jupiter", List.of(
|
||||
position("eth-long", "ETH-PERP", Direction.LONG, "1875.25"),
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "950.00"))),
|
||||
new Venue("phoenix", "Phoenix", List.of(
|
||||
position("btc-long", "BTC-PERP", Direction.LONG, "21000.00"),
|
||||
position("sol-short", "SOL-PERP", Direction.SHORT, "640.50"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,5 +73,6 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
return new Position(id, instrument, direction, new ΩAmountΩ(sizeUsd));
|
||||
}
|
||||
|
||||
private final List<PerpsWalletConfiguration> wallets;
|
||||
private volatile PerpsSnapshot snapshot;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.r35157.evelyn.service.liquidity.impl.ref.EvelynLiquidityServiceImpl;
|
||||
import com.r35157.evelyn.service.status.EvelynStatusService;
|
||||
import com.r35157.evelyn.service.status.impl.ref.EvelynStatusServiceImpl;
|
||||
import com.r35157.evelyn.service.perps.EvelynPerpsService;
|
||||
import com.r35157.evelyn.service.perps.PerpsWalletConfiguration;
|
||||
import com.r35157.evelyn.service.perps.impl.ref.EvelynPerpsServiceImpl;
|
||||
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
||||
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
||||
@@ -360,9 +361,15 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
solanaBlockChain, cis, liquidityConfiguration);
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynPerpsProcess() {
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl();
|
||||
evelynPerpsService = process;
|
||||
private NenjimProcess constructEvelynPerpsProdProcess() {
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_WALLETS);
|
||||
evelynPerpsServiceProd = process;
|
||||
return process;
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynPerpsTestProcess() {
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_WALLETS);
|
||||
evelynPerpsServiceTest = process;
|
||||
return process;
|
||||
}
|
||||
|
||||
@@ -383,9 +390,13 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
EVELYN_STATUS_TEST_ID),
|
||||
requireProcessDependency(
|
||||
evelynPerpsService,
|
||||
evelynPerpsServiceProd,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
EVELYN_PERPS_SERVICE_ID),
|
||||
EVELYN_PERPS_PROD_ID),
|
||||
requireProcessDependency(
|
||||
evelynPerpsServiceTest,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
EVELYN_PERPS_TEST_ID),
|
||||
requireProcessDependency(
|
||||
javaFxRuntimeService,
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
@@ -433,7 +444,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
Map.entry(EVELYN_STATUS_TEST_ID, this::constructEvelynStatusTestProcess),
|
||||
Map.entry(EVELYN_LIQUIDITY_PROD_ID, this::constructEvelynLiquidityProdProcess),
|
||||
Map.entry(EVELYN_IOU_BURNER_PROD_ID, this::constructEvelynIOUBurnerProdProcess),
|
||||
Map.entry(EVELYN_PERPS_SERVICE_ID, this::constructEvelynPerpsProcess),
|
||||
Map.entry(EVELYN_PERPS_PROD_ID, this::constructEvelynPerpsProdProcess),
|
||||
Map.entry(EVELYN_PERPS_TEST_ID, this::constructEvelynPerpsTestProcess),
|
||||
Map.entry(JAVAFX_RUNTIME_SERVICE_ID, this::constructJavaFxRuntimeProcess),
|
||||
Map.entry(EVELYN_MISSION_CONTROL_ID, this::constructMissionControlProcess),
|
||||
Map.entry(PROCESS_MANAGER_ID, this::constructProcessManagerProcess)
|
||||
@@ -551,8 +563,13 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
new NenjimComponentId("evelyn.liquidity.prod");
|
||||
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
|
||||
new NenjimComponentId("evelyn.mission-control.default");
|
||||
private static final NenjimComponentId EVELYN_PERPS_SERVICE_ID =
|
||||
new NenjimComponentId("evelyn.perps.default");
|
||||
private static final NenjimComponentId EVELYN_PERPS_PROD_ID =
|
||||
new NenjimComponentId("evelyn.perps.prod");
|
||||
private static final NenjimComponentId EVELYN_PERPS_TEST_ID =
|
||||
new NenjimComponentId("evelyn.perps.test");
|
||||
private static final List<PerpsWalletConfiguration> EVELYN_PERPS_WALLETS = List.of(
|
||||
new PerpsWalletConfiguration("sample-wallet-a", "Sample wallet A"),
|
||||
new PerpsWalletConfiguration("sample-wallet-b", "Sample wallet B"));
|
||||
private static final NenjimComponentId JAVAFX_RUNTIME_SERVICE_ID =
|
||||
new NenjimComponentId("nenjim.javafx-runtime.service");
|
||||
private static final NenjimComponentId PROCESS_MANAGER_ID =
|
||||
@@ -591,7 +608,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
||||
private EvelynStatusService evelynStatusServiceProd;
|
||||
private EvelynStatusService evelynStatusServiceTest;
|
||||
private EvelynPerpsService evelynPerpsService;
|
||||
private EvelynPerpsService evelynPerpsServiceProd;
|
||||
private EvelynPerpsService evelynPerpsServiceTest;
|
||||
private JupiterSwapService jupiterSwapService;
|
||||
private BoundNotificationService discordAssetAZTokenNotificationService;
|
||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||
|
||||
Reference in New Issue
Block a user