77: Make NenjimHub the runtime owner and replace NenjimApplication with NenjimProcess

This commit is contained in:
2026-09-02 15:59:56 +02:00
parent a252c256c4
commit f5fc5bb3b5
29 changed files with 1012 additions and 461 deletions
+2 -2
View File
@@ -4,9 +4,9 @@ The Jupiter Perps Price Alarm monitors the Jupiter Perps aggregated oracle accou
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy. Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
The Registry service manager currently constructs and registers the alarm as The NenjimHub currently constructs and registers the alarm as
`jupiter-perps-alarm.default`, but deliberately does not start it. Its `jupiter-perps-alarm.default`, but deliberately does not start it. Its
`SolanaWallet` and `JupiterPerpsService` are selected by the manager's hardcoded `SolanaWallet` and `JupiterPerpsService` are selected by the Hub's hardcoded
composition and injected before registration. The alarm action configuration composition and injected before registration. The alarm action configuration
files do not select a wallet or signing key. files do not select a wallet or signing key.
+19 -13
View File
@@ -119,24 +119,30 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
<h2>Components and the central Registry</h2> <h2>Components and the central Registry</h2>
<p>A Nenjim component is a fully constructed building block ready for use. Its consumer decides whether it acts <p>A Nenjim component is a fully constructed building block ready for use. Its consumer decides whether it acts
as a service, application, algorithm, or adapter. <code>NenjimComponent</code> is therefore a pure marker, as a service, application, algorithm, or adapter. A service exposes a domain API, while application describes
while <code>NenjimApplication</code> adds only a common <code>start()</code> operation and no common stop.</p> a user-facing or orchestration role. <code>NenjimComponent</code> is therefore a pure marker. Independently,
the canonical <code>com.r35157.nenjim.process.NenjimProcess</code> marks an active component that the Hub can
start and track; it adds only <code>start()</code> and no common stop, restart, or health operation.</p>
<p>A <code>NenjimComponentId</code> is lower-case lookup metadata owned by one Registry, not a property of the <p>A <code>NenjimComponentId</code> is lower-case lookup metadata owned by one Registry, not a property of the
object, an authorization boundary, or an artifact version. One object may have several IDs. Conversely, one object, an authorization boundary, or an artifact version. One object may have several IDs. Conversely, one
registration is indexed under every component interface implemented by that object, so its typed service and registration is indexed under every component interface implemented by that object, so the Registry object's
application views retain reference identity.</p> typed service and process views retain reference identity.</p>
<p><code>NenjimRegistryService</code> is the injected read-only consumer view. It returns immutable, <p><code>NenjimRegistryService</code> is the injected read-only consumer view. It returns immutable,
registration-ordered ID snapshots for a component interface and supports lookup by explicit ID plus expected registration-ordered ID snapshots for a component interface and supports lookup by explicit ID plus expected
interface. Missing IDs return <code>null</code>; requesting an existing ID through the wrong interface is a interface. Missing IDs return <code>null</code>; requesting an existing ID through the wrong interface is a
caller error. Registration is available only through a package-private administration view retained by the caller error. Registration is available only through a package-private administration view behind the passive
Registry manager.</p> Registry manager. That manager exposes only registration, is neither a component nor a process, is not
<p><code>NenjimRegistryServiceManagerImpl</code> starts and registers the Registry first, registers itself second, registered, and cannot start anything. The internal administration interface must remain package-private so
and then constructs and registers the hardcoded component graph in dependency order. Applications own their an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
selection and activation: the Ticker explicitly selects the Raydium EVE/USDT source ID while the hardcoded <p><code>NenjimHubImpl</code> is the global composition and runtime owner. It constructs the Registry assembly,
source remains registered but inactive. The manager activates only the existing explicit five-application starts and registers the Registry first, records its successful process ID, and then constructs and registers
sequence; it does not start every <code>NenjimApplication</code>. the hardcoded component graph in dependency order. The Ticker explicitly selects the Raydium EVE/USDT source
<code>com.r35157.nenjim.hubd.Main</code> is only the outer Java bootstrap; <code>Main.main(...)</code> starts ID while the hardcoded source remains registered but inactive. The Hub activates only the existing explicit
this manager and is not itself a component or application.</p> five-process sequence after the Registry; it does not start every registered process. Successful process IDs
are exposed as immutable ordered point-in-time snapshots, without implying continued health.</p>
<p><code>com.r35157.nenjim.hubd.Main</code> remains only the outer Java bootstrap. It constructs the reference Hub
through the public <code>NenjimHub</code> interface and calls <code>start()</code> once. The Hub is neither a
component nor a process and is not registered inside its own Registry.</p>
<p>The Registry remains internally open to later registration, but this version has one catalogue and no Context, <p>The Registry remains internally open to later registration, but this version has one catalogue and no Context,
version resolution, classloader integration, discovery, public runtime loading or registration, removal, version resolution, classloader integration, discovery, public runtime loading or registration, removal,
persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, or automatic persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, or automatic
+28 -13
View File
@@ -183,32 +183,47 @@ hints kan på sigt foretrække en lavere kompatibel release.
## 8. Componenter og det centrale Registry ## 8. Componenter og det centrale Registry
En Nenjim component er en færdigkonstrueret byggeklods, som er klar til brug. Rollen som service, applikation, En Nenjim component er en færdigkonstrueret byggeklods, som er klar til brug. Rollen som service, applikation,
algoritme eller adapter bestemmes af den applikation, som bruger componenten; Registry-API'et bruger derfor det algoritme eller adapter og klassifikationen som aktiv proces er uafhængige dimensioner. En service eksponerer et
fælles ord `component` og ikke `plugin`. `NenjimComponent` er en ren marker interface. En startbar component kan domain API, mens en applikation beskriver en user-facing eller orchestration-rolle. Registry-API'et bruger det
desuden implementere `NenjimApplication`, som kun lover `start()` og ikke en fælles stop-operation. fælles ord `component` og ikke `plugin`, og `NenjimComponent` er en ren marker interface. En component, som Hub'en
kan starte og tracke, implementerer desuden den canonical `com.r35157.nenjim.process.NenjimProcess`, som kun lover
`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.
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
registration bliver automatisk synlig gennem alle component interfaces, som objektet implementerer. Registry- og registration bliver automatisk synlig gennem alle component interfaces, som objektet implementerer. Registry-
application-view af Registry-objektet har derfor samme ID og samme object identity. service- og process-view af Registry-objektet har derfor samme ID og samme object identity.
`NenjimRegistryService` er det read-only katalog, som almindelige consumers modtager gennem constructor injection. `NenjimRegistryService` er det read-only katalog, som almindelige consumers modtager gennem constructor injection.
Det kan returnere et immutable registration-order snapshot af ID'er for et component interface eller slå ét ID op Det kan returnere et immutable registration-order snapshot af ID'er for et component interface eller slå ét ID op
gennem et forventet interface. Et manglende ID giver `null`; et eksisterende ID med forkert interface er en caller gennem et forventet interface. Et manglende ID giver `null`; et eksisterende ID med forkert interface er en caller
programming error. Public API'et kan ikke registrere eller fjerne componenter. programming error. Public API'et kan ikke registrere eller fjerne componenter.
`NenjimRegistryServiceManagerImpl` konstruerer og starter Registry'et, registrerer Registry og manager først og `NenjimHubImpl` repræsenterer én kørende Nenjim runtime. Hub'en konstruerer Registry-subsystemet og hele den
konstruerer derefter den nuværende hardcodede component graph i dependency order. Den package-private admin-view er nuværende hardcodede component graph, ejer dependency wiring og vælger de processer, som skal startes. Et
kun managerens registrationshandle og er hverken en component eller public API. Registration starter ikke et implementation-only Registry assembly udleverer read-only service-view, process-view af det samme Registry-objekt
objekt, og Registry'et forbliver internt åbent for senere registration efter startup. og en separat `NenjimRegistryServiceManager`. Manageren er kun Hub'ens passive registrationshandle: den er hverken
component eller process, har ingen `start()` eller service-accessor, registreres ikke og starter aldrig en component.
Den package-private `NenjimRegistryServiceAdmin` er managerens interne mutationsadgang. Den må ikke gøres public,
fordi almindelige consumers ellers kunne caste deres Registry-service til mutationsinterfacet og bryde det
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
registration efter startup.
Applikationen ejer sine konkrete valg og sin activation. Tickerens initiale configuration vælger eksplicit Applikationen ejer sine konkrete valg og sin activation. Tickerens initiale configuration vælger eksplicit
`assetaz.price-source.raydium-pool.eve-usdt` gennem Registry'et; den hardcodede price source er registreret, men `assetaz.price-source.raydium-pool.eve-usdt` gennem Registry'et; den hardcodede price source er registreret, men
inaktiv. Manageren starter kun den nuværende eksplicitte, dependency-safe liste: Ticker, Production Evelyn, Test inaktiv. Hub'en starter Registry-processen først og registrerer den som `nenjim.registry.service`. Derefter starter
Evelyn, Production IOU Burner og Mission Control. Alarmen og øvrige tidligere udkommenterede applikationer bliver Hub'en kun den nuværende eksplicitte, dependency-safe liste: Ticker, Production Evelyn, Test Evelyn, Production IOU
ikke automatisk aktiveret. `com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap; `Main.main(...)` starter Burner og Mission Control. Alarmen og øvrige tidligere udkommenterede applikationer bliver ikke automatisk
manageren gennem `NenjimApplication` og er ikke selv en component eller applikation. aktiveret. Efter hvert succesfuldt `start()` registrerer Hub'en process-ID'et i et immutable, ordered snapshot; et
fejlet start bliver ikke registreret, og listen lover ikke fortsat health eller liveness. Startup er fail-fast og kan
kun forsøges én gang uden generic rollback.
`com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap. `Main.main(...)` konstruerer `NenjimHubImpl` gennem
det globale `NenjimHub`-interface og kalder `start()` præcis én gang. Hub'en ejer også det eksisterende online point
og den afsluttende blocking wait. Hub'en er hverken component eller process og registreres ikke i sit eget Registry.
Det implementerede Registry er ét katalog og er ikke et authorization-system. Contexts, artifact-version semantics, Det implementerede Registry er ét katalog og er ikke et authorization-system. Contexts, artifact-version semantics,
resolution, classloading, automatic discovery, public runtime loading/registration, removal, persistence, events, resolution, classloading, automatic discovery, public runtime loading/registration, removal, persistence, events,
+8 -6
View File
@@ -7,14 +7,16 @@
| Term | Beskrivelse | | Term | Beskrivelse |
|------|-------------| |------|-------------|
| Service | Det offentlige domæne-API, som applikationer og andre services bruger i daglig drift. Det eksponerer almindelige domæneoperationer, men ikke lifecycle, configuration, data-source administration, refresh, parser eller mutation af intern viden. | | Service | Det offentlige domæne-API, som applikationer og andre services bruger i daglig drift. Det eksponerer almindelige domæneoperationer, men ikke lifecycle, configuration, data-source administration, refresh, parser eller mutation af intern viden. En konkret serviceimplementation kan samtidig være en `NenjimProcess`, uden at lifecycle føjes til service-interfacet. |
| ServiceManager | Det offentlige lifecycle- og administrations-API, som en composition/lifecycle owner bruger. De konkrete operationer følger serviceområdet: JournalServiceManager har start, stop, data location, refresh og adgang til JournalService, mens første Registry service manager bevidst kun eksponerer start. Det er ikke det daglige domain/query-API for almindelige consumers. | | ServiceManager | Det offentlige lifecycle- eller administrations-API, som en composition/lifecycle owner bruger. De konkrete operationer følger serviceområdet: JournalServiceManager har start, stop, data location, refresh og adgang til JournalService, mens Registry service manageren kun er et passivt registrationshandle. Det er ikke det daglige domain/query-API for almindelige consumers. |
| Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. Dens konkrete rolle som service, applikation, algoritme eller adapter bestemmes af consumeren; `NenjimComponent` er en ren marker uden ID eller lifecycle state. | | Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. `NenjimComponent` er en ren marker uden ID eller lifecycle state. Service, applikation, algoritme og adapter beskriver roller, mens process beskriver en uafhængig lifecycle-klassifikation. |
| NenjimApplication | Et component interface med én fælles `start()`-operation. Første version lover ikke et fælles `stop()`. En konkret serviceimplementation kan implementere dette interface uden at lifecycle føjes til dens domain service interface. | | Applikation | En user-facing eller orchestration-rolle. Rollen gør ikke i sig selv en component til en process; en aktiv applikationsimplementation kan separat implementere `NenjimProcess`. |
| NenjimProcess | Det canonical `com.r35157.nenjim.process.NenjimProcess` component interface for noget, som Hub'en kan starte og tracke. Det eksponerer kun `start()` og lover ikke stop, restart, health, Context eller dependency lookup. |
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en konstruerer og registrerer component graph'en, ejer dependency wiring og startup-policy, tracker succesfuldt startede process-ID'er, erklærer online point og ejer den afsluttende runtime wait. Den er hverken component eller process og registreres ikke i sit eget Registry. |
| 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 registrationshandle i referenceimplementationen. Det er ikke en component, er ikke indexeret og udleveres ikke til almindelige consumers. | | 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. |
| NenjimRegistryServiceManager | Public lifecycle/composition API med kun `start()` i første version. Referenceimplementationen konstruerer Registry og den hardcodede component graph, registrerer ID'er og aktiverer kun den eksplicit valgte application subset. | | NenjimRegistryServiceManager | Public, passivt administrationshandle med kun `registerComponent(...)`. Det holdes privat af Hub'en, delegerer til det interne admin-view, er hverken component eller process, registreres ikke og ejer ingen lifecycle eller composition. |
| Component registration | Bindingen mellem ét `NenjimComponentId` og ét object. Én registration indexeres under alle objektets component interfaces; flere forskellige ID'er må pege på præcis samme object instance. | | Component registration | Bindingen mellem ét `NenjimComponentId` og ét object. Én registration indexeres under alle objektets component interfaces; flere forskellige ID'er må pege på præcis samme object instance. |
| Journal | Et immutable, komplet snapshot af al aktuel viden om præcis én artifakt og dens kendte releases. Journalen er aldrig en delta. | | Journal | Et immutable, komplet snapshot af al aktuel viden om præcis én artifakt og dens kendte releases. Journalen er aldrig en delta. |
| ArtifactCoordinate | Journal-kædens stabile identitet, udledt som `<GROUP>-<MODULE>-<ARTIFACT>`, f.eks. `com_r35157_nenjim-hubd-api`. Der findes intet separat `JournalId`. | | ArtifactCoordinate | Journal-kædens stabile identitet, udledt som `<GROUP>-<MODULE>-<ARTIFACT>`, f.eks. `com_r35157_nenjim-hubd-api`. Der findes intet separat `JournalId`. |
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-02
@@ -0,0 +1,72 @@
## Context
See `proposal.md` for motivation and the delta specification for observable behavior. At baseline, `NenjimRegistryServiceManagerImpl` mixes three concerns: privileged Registry registration, construction of the complete hardcoded object graph, and runtime lifecycle. The thin `Main` starts that manager through `NenjimApplication`, while the Registry object itself also implements the same lifecycle type. Registry storage and its mutation view are package-private; the new Hub implementation must remain in its own implementation package without weakening that boundary.
Human-written Java remains in `.tjava`, Detag runs through the normal Gradle build, and the exact operational component bindings from issue #76 must be preserved without executing production startup as verification. Legacy process and Hub prototypes under `crypto.r35157` and `com.r35157.nenjim.kicker` remain outside the modern API migration.
## Goals / Non-Goals
**Goals:**
- Make one `NenjimHubImpl` the unmistakable composition root and runtime lifecycle owner.
- Keep Registry mutation inaccessible from ordinary `NenjimRegistryService` consumers while allowing a Hub in another package to receive a narrow manager handle.
- Preserve the exact current graph, enabled sequence, inactive set, online point, wait behavior, and Registry indexing semantics.
- Make startup tracking linearizable enough for concurrent snapshot readers and truthful under partial failure.
- Keep public module packages extractable and explicitly nullable where reference values cross new or changed APIs.
**Non-Goals:**
- No configurable graph, dependency resolver, automatic process discovery/start-all, generic rollback, or process health inference.
- No stop/restart contract, test hooks, permanent automated tests, Journal integration, Process Manager wiring, or legacy prototype cleanup.
- No change to embedded operational values, business constructors, or component behavior while relocating composition.
## Decisions
### NenjimProcess is the only modern common lifecycle type
Add `com.r35157.nenjim.process.NenjimProcess` as a marker-derived interface with only `start() throws Exception`. Replace every modern `NenjimApplication` implementation and lookup, then delete `NenjimApplication` rather than retaining an alias. Service APIs stay domain-focused, and application remains a descriptive role. Keeping an alias was rejected because it would preserve two canonical lifecycle classifications and make future Registry queries ambiguous.
Legacy `crypto.r35157.nenjim.NenjimProcess` and `com.r35157.nenjim.kicker.NenjimProcess` are not imported by the modern graph and remain untouched. Their packages and contracts are explicitly non-canonical and outside issue #77.
### A small implementation-only assembly crosses the Registry package boundary
Add a public reference-implementation assembly type under `com.r35157.nenjim.service.registry.impl.ref` with a static construction operation and three read-only accessors. It creates one package-private `NenjimRegistryServiceImpl`, exposes that identical object as both `NenjimRegistryService` and `NenjimProcess`, and creates a distinct `NenjimRegistryServiceManagerImpl` holding only the package-private admin view.
The assembly exposes neither the implementation nor `NenjimRegistryServiceAdmin`, has no lifecycle methods, and is not a component. Its constructor is private, so callers cannot manufacture inconsistent triples. Returning the manager from `NenjimRegistryService` or making the admin interface public was rejected because either would let ordinary consumers escalate a read-only service reference into mutation access.
### The Registry manager delegates one operation and owns no state policy
`NenjimRegistryServiceManagerImpl` retains only a required `NenjimRegistryServiceAdmin` reference and delegates `registerComponent(...)`. Its constructor remains inaccessible outside the reference implementation package; the Hub obtains it through the assembly. It has no start flag, Registry getter, component marker, graph fields, IDs, logging, or wait latch. Registration remains synchronized and atomic inside the existing Registry implementation.
### NenjimHubImpl owns the moved graph and explicit startup sequence
Move the complete existing composition code and its exact constants into `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl`. At the beginning of the one accepted `start()` attempt, the Hub obtains the Registry views and private manager, starts the Registry process, registers the Registry service, and records its ID. Only then does it build and register the remaining graph through the manager, because the Ticker constructor performs a Registry lookup.
After composition, a helper resolves each explicitly enabled ID as `NenjimProcess`, calls `start()`, and records the ID only after successful return. Keeping an explicit sequence rather than iterating every process registration preserves deliberately inactive components and avoids inventing dependency ordering.
### Snapshot state uses one lock and copy-on-read
The Hub stores successful IDs in an insertion-ordered `ArrayList`. A small private synchronized append operation and a synchronized `getStartedProcessIds()` returning `List.copyOf(...)` make calls safe before, during, and after startup while preventing callers or later starts from mutating earlier snapshots. `AtomicBoolean.compareAndSet` consumes the Hub's single start attempt before any work, including attempts that later fail.
This is intentionally startup history, not a running/healthy registry. Concurrent collections or a richer process-state model were rejected because there is one writer, a tiny list, and no common stop or health signal.
### Main remains a two-line runtime delegation
`com.r35157.nenjim.hubd.Main` constructs `NenjimHubImpl` into a `NenjimHub` variable and invokes `start()` once. It does not retain or expose Registry views. Gradle's configured entry point stays unchanged.
## Risks / Trade-offs
- [Relocating a large hardcoded graph can accidentally change an operational binding] → Move the graph mechanically, keep constants and constructor arguments unchanged, and inspect a redacted final diff against issue #76.
- [A later startup failure leaves earlier processes active] → Preserve fail-fast propagation and truthful successful history; do not invent rollback without a common stop contract.
- [The implementation-only assembly is publicly nameable] → Keep it under `.impl.ref`, make construction internally consistent, expose no admin type, and provide no reverse path from an active service view to its manager.
- [The Hub's final wait makes ordinary end-to-end execution unsafe and non-terminating] → Validate through compilation, strict OpenSpec checks, static acceptance auditing, and narrowly scoped temporary probes that do not invoke production startup.
- [Legacy similarly named process types remain] → Document the canonical fully qualified name and limit migration to modern packages, as required by issue #77.
## Migration Plan
1. Add the canonical process and Hub APIs plus the Registry assembly/passive manager boundary.
2. Move the existing graph and runtime sequence into `NenjimHubImpl`, replace modern lifecycle implementations/lookups, and update `Main`.
3. Update directly affected architecture, terminology, HTML, and JavaDoc without changing archived OpenSpec history.
4. Strictly validate the active change, build through Detag and Java, run safe narrow probes, and audit the redacted diff while leaving the change active.
Rollback before review is normal source-control reversal of these uncommitted files. No persistent data or external Registry state is migrated.
@@ -0,0 +1,32 @@
## Why
Issue #76 made the Registry manager the composition root and lifecycle owner, but design review established that these are responsibilities of one running NenjimHub rather than of the Registry subsystem. The architecture now needs a canonical process contract and an explicit Hub runtime owner before later Journal and process-management work builds on the wrong boundary.
## What Changes
- **BREAKING**: Replace `NenjimApplication` with the canonical minimal `NenjimProcess` component contract.
- Reintroduce `NenjimHub` as the global composition and runtime owner, with single-attempt startup and an immutable ordered snapshot of successfully started process IDs.
- Move Registry construction, the complete hardcoded component graph, explicit process startup, the online point, and the blocking runtime wait into `NenjimHubImpl`.
- **BREAKING**: Reduce `NenjimRegistryServiceManager` to a passive registration handle that is retained by the Hub and is neither a component nor a process.
- Add an implementation-only Registry construction boundary that provides read-only service, process, and manager views without exposing the package-private administration interface.
- Preserve all current component bindings, enabled process choices and order, inactive components, Registry lookup guarantees, and fail-fast startup behavior.
- Keep `com.r35157.nenjim.hubd.Main` as the thin configured entry point and update directly affected documentation and JavaDoc.
## Capabilities
### New Capabilities
None.
### Modified Capabilities
- `nenjim-component-registry`: Corrects component lifecycle terminology, Registry-manager responsibility, global Hub ownership, startup tracking, and outer bootstrap behavior.
- `assetaz-currency-identity-service`: Transfers hardcoded catalogue composition ownership from the Registry manager to `NenjimHub`.
- `assetaz-ticker-service`: Transfers price-source construction and registration ownership from the Registry manager to `NenjimHub`.
## Impact
- Changes public lifecycle and runtime APIs under `com.r35157.nenjim.process`, `com.r35157.nenjim.hubd`, and `com.r35157.nenjim.service.registry`.
- Moves the current reference composition from `NenjimRegistryServiceManagerImpl` to `NenjimHubImpl` while preserving its operational bindings and sequence.
- Updates all modern startable reference implementations to the new process contract and removes `NenjimApplication` without a compatibility alias.
- Updates the active OpenSpec delta, Nenjim architecture documentation, terminology, and public HTML; no Journal, Context, classloader, configurable autorun, process-manager, stop/restart/health, or business behavior is introduced.
@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: Immutable conflict-free hardcoded catalogue
The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. The NenjimHub SHALL create exactly one hardcoded service instance for the current hardcoded component graph, register it as `assetaz.currency-identity.hardcoded`, and SHALL pass that same instance to every component in that graph that requires currency identities.
#### Scenario: Conflicting external mapping
- **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs
- **THEN** initialization fails with a clear conflict exception
#### Scenario: Conflicting currency metadata
- **WHEN** initialization provides conflicting current values for the same UUID
- **THEN** initialization fails with a clear conflict exception
#### Scenario: Compose hardcoded components
- **WHEN** the NenjimHub constructs the current hardcoded component graph
- **THEN** it creates and registers one hardcoded Currency Identity Service and injects that same instance into every component that requires it
@@ -0,0 +1,40 @@
## 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 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
- **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 ticker lifecycle and latest-price queries but no source-registration operation
#### 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** the ticker starts and later stops with an active Registry-resolved source
- **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
- **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
@@ -0,0 +1,158 @@
## ADDED Requirements
### 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.
#### Scenario: One object has more than one registration identity
- **WHEN** the same component object is registered under two different unused component IDs
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
#### Scenario: A service implementation is startable
- **WHEN** a service implementation has a no-argument active startup lifecycle managed by the Hub
- **THEN** the implementation is discoverable as `NenjimProcess` without adding lifecycle operations solely to its domain service interface
#### Scenario: Application role and process lifecycle are independent
- **WHEN** a component performs a user-facing or orchestration application role
- **THEN** that role alone neither makes it a process nor changes its domain interfaces
#### Scenario: Common stop is unavailable
- **WHEN** a caller uses `NenjimProcess`
- **THEN** the contract exposes no stop, restart, health, context, or dependency-lookup operation
### Requirement: Registry construction preserves the administrative boundary
The reference Registry construction boundary SHALL create one Registry object exposed through both `NenjimRegistryService` and `NenjimProcess`, plus one distinct `NenjimRegistryServiceManager` bound to that Registry. The service and process views SHALL preserve object identity. The construction helper SHALL own no lifecycle, SHALL be neither a component nor a process, SHALL not be Registry-discoverable, and SHALL expose neither the package-private administration interface nor a way to derive the active Registry's manager from an ordinary service reference.
#### Scenario: Registry views preserve identity
- **WHEN** the Hub obtains a newly constructed Registry assembly
- **THEN** its service and process views reference the identical Registry object while its manager is a different object
#### Scenario: Ordinary service reference remains read-only
- **WHEN** an ordinary consumer receives the public Registry service view
- **THEN** the consumer cannot name or derive the package-private administration view or obtain the manager bound to that active Registry
### Requirement: Registry manager is a passive registration handle
`NenjimRegistryServiceManager` SHALL expose exactly registration by component ID and component. It SHALL be neither a Nenjim component nor a Nenjim process, SHALL remain privately retained by the Hub, and SHALL delegate registration to the Registry's internal administration view while preserving duplicate-ID, interface-indexing, and post-start registration behavior. It SHALL not construct the business graph, own lifecycle, start or stop any process, expose the Registry service, or be registered under `nenjim.registry.service-manager` or any other component ID.
#### Scenario: Hub registers a component
- **WHEN** the Hub passes an unused component ID and a non-null component to the manager
- **THEN** the component becomes available through the Registry's existing typed interface index without being activated
#### Scenario: Registry manager is not discoverable
- **WHEN** Registry registrations are inspected after Hub composition
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
### Requirement: NenjimHub owns composition and runtime startup
`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 complete hardcoded component set from issue #76 in dependency order, resolve only the explicitly enabled IDs through `NenjimProcess`, start them in dependency-safe order, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup.
#### Scenario: Registry starts before graph construction
- **WHEN** the Hub performs its first startup attempt
- **THEN** it starts the Registry, registers it as `nenjim.registry.service`, and records that ID before constructing components whose constructors may query the Registry
#### Scenario: Registry is process-discoverable
- **WHEN** the Registry registration has completed
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
#### Scenario: Required hardcoded bindings are registered
- **WHEN** Hub composition completes
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding the former Registry-manager ID
#### Scenario: Explicit startup subset preserves order
- **WHEN** the Hub starts processes after the Registry
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
#### Scenario: Deliberately inactive processes remain inactive
- **WHEN** Hub startup reaches its online point
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
#### Scenario: Hub start is not retried
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
#### Scenario: Startup failure is fail-fast
- **WHEN** a process startup throws
- **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
`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. 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.
#### Scenario: Snapshot before process startup
- **WHEN** the snapshot is requested before any process has started successfully
- **THEN** it is an empty immutable list
#### Scenario: Successful process is recorded
- **WHEN** a process's `start()` call returns successfully
- **THEN** its ID appears after all previously successful process IDs in every later snapshot
#### Scenario: Failed process is not recorded
- **WHEN** a process's `start()` call throws
- **THEN** its ID is absent while all earlier successful process IDs remain visible
#### Scenario: Snapshot is a point-in-time value
- **WHEN** the Hub starts another process after a caller obtained a snapshot
- **THEN** the earlier snapshot is unchanged and cannot be mutated by its caller
## MODIFIED Requirements
### Requirement: Registry startup is single-use and gates queries
The Registry process's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.
#### Scenario: Query before startup
- **WHEN** either lookup operation is called before successful Registry startup
- **THEN** it fails with `IllegalStateException`
#### Scenario: Registry starts once
- **WHEN** the Registry is started successfully and start is requested again
- **THEN** the second request fails with `IllegalStateException`
#### Scenario: Failed start is not retried
- **WHEN** the first Registry start attempt fails
- **THEN** every later start request fails with `IllegalStateException`
### 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 construct the Registry, manager, or business graph.
#### Scenario: Start through the outer bootstrap
- **WHEN** the configured Java entry point is invoked
- **THEN** `Main.main(...)` constructs one reference Hub through the public Hub interface and delegates startup to it exactly once
#### Scenario: No duplicate composition path remains
- **WHEN** the source tree is inspected after migration
- **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
- **WHEN** the outer bootstrap source is inspected
- **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
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.
#### Scenario: Ticker source packages are inspected
- **WHEN** source packages and imports are searched after migration
- **THEN** no directly affected Ticker price-source package uses the `plugins` segment
#### Scenario: Internal Registry administration is inspected
- **WHEN** an ordinary consumer compiles against the public Registry packages
- **THEN** the package-private administration interface and Registry implementation are inaccessible, and the public service exposes no path to its active manager
#### Scenario: Canonical process API is inspected
- **WHEN** modern source packages are searched after migration
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
### 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, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. The Hub SHALL not add configurable autorun, process-manager integration, stop, restart, upgrade, health monitoring, recovery, or context-bound dependency access.
#### Scenario: Consumer inspects the first Registry API
- **WHEN** a consumer examines the public Registry, Registry-manager, process, and Hub contracts
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, and Hub startup/snapshot operations are available and no deferred feature is exposed
## REMOVED Requirements
### Requirement: Components and applications have minimal common contracts
**Reason**: `NenjimApplication` incorrectly combines application role with active lifecycle classification and is replaced by the canonical `NenjimProcess` contract.
**Migration**: Modern startable implementations and typed Registry lookups use `com.r35157.nenjim.process.NenjimProcess`; service and application remain descriptive roles rather than common lifecycle interfaces.
### Requirement: Registry manager owns hardcoded composition and startup
**Reason**: Composition, startup policy, started-process state, the online point, and the blocking runtime lifecycle belong to one global NenjimHub, not to the Registry subsystem.
**Migration**: `NenjimHubImpl` owns the existing graph and startup sequence; `NenjimRegistryServiceManager` becomes an unregistered passive registration handle.
@@ -0,0 +1,26 @@
## 1. Lifecycle and Registry Boundaries
- [x] 1.1 Add the canonical minimal `NenjimProcess`, migrate every modern startable implementation and lookup, and remove `NenjimApplication` without an alias.
- [x] 1.2 Reduce the public Registry manager contract and reference implementation to passive registration only.
- [x] 1.3 Add the implementation-only Registry assembly that preserves service/process identity and keeps administration package-private.
- [x] 1.4 Strengthen changed API and internal-admin JavaDoc, including the warning against publicly exposing the Registry mutation interface.
## 2. Hub Composition and Runtime Ownership
- [x] 2.1 Add the public `NenjimHub` contract with single-start delegation and an immutable ordered started-process snapshot.
- [x] 2.2 Move the complete hardcoded component graph and all registration into `NenjimHubImpl` without changing component bindings.
- [x] 2.3 Start and record the Registry first, then preserve the exact enabled process order, inactive set, fail-fast behavior, online point, and blocking wait.
- [x] 2.4 Update `Main` to construct and start exactly one `NenjimHub` while keeping the configured entry point unchanged.
## 3. Documentation
- [x] 3.1 Update `docs/Nenjim.md` and `docs/Terminologi.md` to distinguish component, service, application role, process lifecycle, Hub ownership, and passive Registry administration.
- [x] 3.2 Update directly affected public HTML and operational documentation without changing deferred-feature or business behavior.
- [x] 3.3 Add ownership deltas for AssetAZ Currency Identity and Ticker so their composition requirements name `NenjimHub` rather than the Registry manager.
## 4. Verification
- [x] 4.1 Strictly validate the active OpenSpec change and all repository specs without synchronizing or archiving the change.
- [x] 4.2 Run the existing Gradle build and Detag/Java compilation without invoking production `main()`.
- [x] 4.3 Run safe temporary probes for Registry assembly identity/admin boundaries and pre-start immutable Hub snapshots, then remove all probe artifacts.
- [x] 4.4 Inspect the complete redacted diff, working tree, package references, generated-source status, acceptance criteria, and out-of-scope boundaries without committing or pushing.
@@ -3,9 +3,7 @@
## Purpose ## Purpose
Provides one authoritative AssetAZ identity model for currencies and translates stable UUIDs and namespaced external identifiers without treating display symbols as identity. Provides one authoritative AssetAZ identity model for currencies and translates stable UUIDs and namespaced external identifiers without treating display symbols as identity.
## Requirements ## Requirements
### Requirement: Stable AssetAZ currency identity ### Requirement: Stable AssetAZ currency identity
The system SHALL represent a currency as an immutable `CurrencyType` with a non-null UUID, name, and symbol. Its UUID is its complete stable identity. Equality and hash codes SHALL depend only on that UUID, regardless of name, symbol, instance identity, or metadata changes. The system SHALL represent a currency as an immutable `CurrencyType` with a non-null UUID, name, and symbol. Its UUID is its complete stable identity. Equality and hash codes SHALL depend only on that UUID, regardless of name, symbol, instance identity, or metadata changes.
@@ -67,7 +65,7 @@ The Currency Identity Service SHALL return all configured external references fo
- **THEN** the result contains its configured Solana mint reference - **THEN** the result contains its configured Solana mint reference
### Requirement: Immutable conflict-free hardcoded catalogue ### Requirement: Immutable conflict-free hardcoded catalogue
The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. The Nenjim Registry service manager SHALL create exactly one hardcoded service instance for the current hardcoded component graph, register it as `assetaz.currency-identity.hardcoded`, and SHALL pass that same instance to every component in that graph that requires currency identities. The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. The NenjimHub SHALL create exactly one hardcoded service instance for the current hardcoded component graph, register it as `assetaz.currency-identity.hardcoded`, and SHALL pass that same instance to every component in that graph that requires currency identities.
#### Scenario: Conflicting external mapping #### Scenario: Conflicting external mapping
- **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs - **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs
@@ -78,7 +76,7 @@ The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ curr
- **THEN** initialization fails with a clear conflict exception - **THEN** initialization fails with a clear conflict exception
#### Scenario: Compose hardcoded components #### Scenario: Compose hardcoded components
- **WHEN** the Registry service manager constructs the current hardcoded component graph - **WHEN** the NenjimHub constructs the current hardcoded component graph
- **THEN** it creates and registers one hardcoded Currency Identity Service and injects that same instance into every component that requires it - **THEN** it creates and registers one hardcoded Currency Identity Service and injects that same instance into every component that requires it
### Requirement: Trading pairs use service-owned identities ### Requirement: Trading pairs use service-owned identities
@@ -3,14 +3,12 @@
## Purpose ## Purpose
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 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. 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 Registry manager 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
- **THEN** source construction requires neither a `PriceSink` nor a ticker reference and leaves the source unstarted - **THEN** source construction requires neither a `PriceSink` nor a ticker reference and leaves the source unstarted
#### Scenario: Ticker resolves configured source components #### Scenario: Ticker resolves configured source components
+108 -53
View File
@@ -3,24 +3,7 @@
## Purpose ## Purpose
Provide one typed, read-only catalogue of constructed Nenjim components and a lifecycle manager that owns the current hardcoded composition without introducing Context or dynamic-loading semantics. Provide one typed, read-only catalogue of constructed Nenjim components and a lifecycle manager that owns the current hardcoded composition without introducing Context or dynamic-loading semantics.
## Requirements ## Requirements
### Requirement: Components and applications have minimal common contracts
`NenjimComponent` SHALL be a pure marker with no component-ID property. `NenjimApplication` SHALL extend `NenjimComponent` and expose only `start() throws Exception` as its common lifecycle operation. Domain service interfaces SHALL remain focused on their domain APIs, while a concrete service implementation with a no-argument active start lifecycle SHALL additionally implement `NenjimApplication`.
#### Scenario: One object has more than one registration identity
- **WHEN** the same component object is registered under two different unused component IDs
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
#### Scenario: A service implementation is startable
- **WHEN** a service implementation has a no-argument active start lifecycle
- **THEN** the implementation is discoverable as `NenjimApplication` without adding lifecycle operations solely to its domain service interface
#### Scenario: Common stop is unavailable
- **WHEN** a caller uses `NenjimApplication`
- **THEN** the contract exposes no common stop operation
### Requirement: Component IDs are canonical validated values ### Requirement: Component IDs are canonical validated values
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality. `NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
@@ -95,7 +78,7 @@ The internal Registry administration view SHALL remain package-private, SHALL no
- **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged - **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged
### Requirement: Registry startup is single-use and gates queries ### Requirement: Registry startup is single-use and gates queries
The Registry application's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component. The Registry process's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.
#### Scenario: Query before startup #### Scenario: Query before startup
- **WHEN** either lookup operation is called before successful Registry startup - **WHEN** either lookup operation is called before successful Registry startup
@@ -109,33 +92,6 @@ The Registry application's first `start()` call SHALL be the only accepted attem
- **WHEN** the first Registry start attempt fails - **WHEN** the first Registry start attempt fails
- **THEN** every later start request fails with `IllegalStateException` - **THEN** every later start request fails with `IllegalStateException`
### Requirement: Registry manager owns hardcoded composition and startup
`NenjimRegistryServiceManager` SHALL be a Nenjim component whose only public operation is `start() throws Exception`. Its public reference implementation SHALL be a distinct `NenjimApplication` object and SHALL accept only one start attempt, including after failure. The first attempt SHALL construct and directly start one Registry object, register that object first as `nenjim.registry.service`, register the manager second as `nenjim.registry.service-manager`, construct and register the complete required hardcoded component set in dependency order, start only the explicit active application subset in dependency-safe order, announce the existing online point, and preserve the existing blocking wait.
#### Scenario: Registry and manager views are registered
- **WHEN** manager composition reaches completed registration
- **THEN** the Registry object is retrievable by one ID as both `NenjimRegistryService` and `NenjimApplication`, the manager is retrievable by one different ID as both `NenjimRegistryServiceManager` and `NenjimApplication`, and the Registry and manager are different instances
#### Scenario: Required hardcoded bindings are registered
- **WHEN** manager composition completes
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components
#### Scenario: Explicit startup subset preserves order
- **WHEN** the manager starts applications
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
#### Scenario: Deliberately inactive applications remain inactive
- **WHEN** manager startup completes its explicit start sequence
- **THEN** `jupiter-perps-alarm.default` and the currently commented Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client are not started
#### Scenario: Manager start is not retried
- **WHEN** any first manager start attempt has begun and start is requested again
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
#### Scenario: Registration does not activate components
- **WHEN** the manager registers a component
- **THEN** registration alone neither starts nor stops that component
### Requirement: Applications own explicit dependency selection ### Requirement: Applications own explicit dependency selection
A non-bootstrap application or service that owns a component choice SHALL receive the public Registry view through constructor injection, express the choice as one or more explicit component IDs, and use typed lookup. A required missing or wrong-type binding SHALL fail clearly rather than selecting the first component returned for an interface. The initial Ticker composition SHALL explicitly select only `assetaz.price-source.raydium-pool.eve-usdt`; `assetaz.price-source.hardcoded` SHALL remain registered but inactive. A non-bootstrap application or service that owns a component choice SHALL receive the public Registry view through constructor injection, express the choice as one or more explicit component IDs, and use typed lookup. A required missing or wrong-type binding SHALL fail clearly rather than selecting the first component returned for an interface. The initial Ticker composition SHALL explicitly select only `assetaz.price-source.raydium-pool.eve-usdt`; `assetaz.price-source.hardcoded` SHALL remain registered but inactive.
@@ -152,18 +108,22 @@ 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 that does not implement `NenjimApplication`. Its `main(String[])` SHALL construct `NenjimRegistryServiceManagerImpl` through a variable typed as `NenjimApplication` and start it. The Gradle application entry point SHALL name this class. Construction, registration, composition, and lifecycle ownership SHALL remain in `NenjimRegistryServiceManagerImpl`; the bootstrap SHALL perform none of those responsibilities beyond constructing and starting the manager. `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
- **THEN** `Main.main(...)` constructs one Registry manager as a Nenjim application and delegates startup to it - **THEN** `Main.main(...)` constructs one reference Hub through the public Hub interface and delegates startup to it exactly once
#### 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 old Hub interface, `com.r35157.nenjim.hubd.impl.ref.Main`, and the `NenjimHubImpl` composition implementation are absent, while `com.r35157.nenjim.hubd.Main` contains no replacement composition path - **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
- **WHEN** the outer bootstrap source is inspected
- **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`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; the component ID SHALL live in its `.valuetypes` package; and internal administration and Registry storage SHALL live in `.impl.ref`. Directly affected Ticker price-source API and implementation packages SHALL use `.pricesource` rather than `.plugins.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.
#### Scenario: Ticker source packages are inspected #### Scenario: Ticker source packages are inspected
- **WHEN** source packages and imports are searched after migration - **WHEN** source packages and imports are searched after migration
@@ -171,11 +131,106 @@ Public component contracts SHALL live in `com.r35157.nenjim.component`; public R
#### Scenario: Internal Registry administration is inspected #### Scenario: Internal Registry administration is inspected
- **WHEN** an ordinary consumer compiles against the public Registry packages - **WHEN** an ordinary consumer compiles against the public Registry packages
- **THEN** the package-private administration interface and Registry implementation are inaccessible - **THEN** the package-private administration interface and Registry implementation are inaccessible, and the public service exposes no path to its active manager
#### Scenario: Canonical process API is inspected
- **WHEN** modern source packages are searched after migration
- **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, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. 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. The Hub SHALL not add configurable autorun, process-manager integration, 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 and manager contracts - **WHEN** a consumer examines the public Registry, Registry-manager, process, and Hub contracts
- **THEN** only the agreed read-only lookup and manager start operations are available and no deferred feature is exposed - **THEN** only read-only Registry lookup, passive registration, minimal process startup, and Hub startup/snapshot operations are available and no deferred feature is exposed
### 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.
#### Scenario: One object has more than one registration identity
- **WHEN** the same component object is registered under two different unused component IDs
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
#### Scenario: A service implementation is startable
- **WHEN** a service implementation has a no-argument active startup lifecycle managed by the Hub
- **THEN** the implementation is discoverable as `NenjimProcess` without adding lifecycle operations solely to its domain service interface
#### Scenario: Application role and process lifecycle are independent
- **WHEN** a component performs a user-facing or orchestration application role
- **THEN** that role alone neither makes it a process nor changes its domain interfaces
#### Scenario: Common stop is unavailable
- **WHEN** a caller uses `NenjimProcess`
- **THEN** the contract exposes no stop, restart, health, context, or dependency-lookup operation
### Requirement: Registry construction preserves the administrative boundary
The reference Registry construction boundary SHALL create one Registry object exposed through both `NenjimRegistryService` and `NenjimProcess`, plus one distinct `NenjimRegistryServiceManager` bound to that Registry. The service and process views SHALL preserve object identity. The construction helper SHALL own no lifecycle, SHALL be neither a component nor a process, SHALL not be Registry-discoverable, and SHALL expose neither the package-private administration interface nor a way to derive the active Registry's manager from an ordinary service reference.
#### Scenario: Registry views preserve identity
- **WHEN** the Hub obtains a newly constructed Registry assembly
- **THEN** its service and process views reference the identical Registry object while its manager is a different object
#### Scenario: Ordinary service reference remains read-only
- **WHEN** an ordinary consumer receives the public Registry service view
- **THEN** the consumer cannot name or derive the package-private administration view or obtain the manager bound to that active Registry
### Requirement: Registry manager is a passive registration handle
`NenjimRegistryServiceManager` SHALL expose exactly registration by component ID and component. It SHALL be neither a Nenjim component nor a Nenjim process, SHALL remain privately retained by the Hub, and SHALL delegate registration to the Registry's internal administration view while preserving duplicate-ID, interface-indexing, and post-start registration behavior. It SHALL not construct the business graph, own lifecycle, start or stop any process, expose the Registry service, or be registered under `nenjim.registry.service-manager` or any other component ID.
#### Scenario: Hub registers a component
- **WHEN** the Hub passes an unused component ID and a non-null component to the manager
- **THEN** the component becomes available through the Registry's existing typed interface index without being activated
#### Scenario: Registry manager is not discoverable
- **WHEN** Registry registrations are inspected after Hub composition
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
### Requirement: NenjimHub owns composition and runtime startup
`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 complete hardcoded component set from issue #76 in dependency order, resolve only the explicitly enabled IDs through `NenjimProcess`, start them in dependency-safe order, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup.
#### Scenario: Registry starts before graph construction
- **WHEN** the Hub performs its first startup attempt
- **THEN** it starts the Registry, registers it as `nenjim.registry.service`, and records that ID before constructing components whose constructors may query the Registry
#### Scenario: Registry is process-discoverable
- **WHEN** the Registry registration has completed
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
#### Scenario: Required hardcoded bindings are registered
- **WHEN** Hub composition completes
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding the former Registry-manager ID
#### Scenario: Explicit startup subset preserves order
- **WHEN** the Hub starts processes after the Registry
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
#### Scenario: Deliberately inactive processes remain inactive
- **WHEN** Hub startup reaches its online point
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
#### Scenario: Hub start is not retried
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
#### Scenario: Startup failure is fail-fast
- **WHEN** a process startup throws
- **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
`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. 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.
#### Scenario: Snapshot before process startup
- **WHEN** the snapshot is requested before any process has started successfully
- **THEN** it is an empty immutable list
#### Scenario: Successful process is recorded
- **WHEN** a process's `start()` call returns successfully
- **THEN** its ID appears after all previously successful process IDs in every later snapshot
#### Scenario: Failed process is not recorded
- **WHEN** a process's `start()` call throws
- **THEN** its ID is absent while all earlier successful process IDs remain visible
#### Scenario: Snapshot is a point-in-time value
- **WHEN** the Hub starts another process after a caller obtained a snapshot
- **THEN** the earlier snapshot is unchanged and cannot be mutated by its caller
@@ -15,7 +15,7 @@ import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.TradingPair; import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.assetaz.services.ticker.PriceObservation; import com.r35157.assetaz.services.ticker.PriceObservation;
import com.r35157.assetaz.services.ticker.TickerService; import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.process.NenjimProcess;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -46,7 +46,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM;
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM; import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM;
public class EvelynImpl implements Evelyn, NenjimApplication { public class EvelynImpl implements Evelyn, NenjimProcess {
public EvelynImpl( public EvelynImpl(
@NotNull String instanceName, @NotNull String instanceName,
@@ -18,7 +18,7 @@ import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
import com.r35157.libs.valuetypes.basic.AssetPrice; import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.TradingPair; import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.process.NenjimProcess;
import com.r35157.service.notification.BoundNotificationService; import com.r35157.service.notification.BoundNotificationService;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -49,7 +49,7 @@ import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_M
* Reference implementation of the Evelyn IOU buyback-and-burn lifecycle. * Reference implementation of the Evelyn IOU buyback-and-burn lifecycle.
*/ */
public final class EvelynIOUBurnerServiceImpl public final class EvelynIOUBurnerServiceImpl
implements EvelynIOUBurnerService, NenjimApplication { implements EvelynIOUBurnerService, NenjimProcess {
/** /**
* Creates an Evelyn IOU burner with all runtime dependencies and current * Creates an Evelyn IOU burner with all runtime dependencies and current
* operating policy. * operating policy.
@@ -6,7 +6,7 @@ import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService; import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.libs.valuetypes.basic.AssetPrice; import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.TradingPair; import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.process.NenjimProcess;
import com.r35157.nenjim.service.registry.NenjimRegistryService; import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId; import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class TickerServiceImpl implements public final class TickerServiceImpl implements
TickerService, TickerService,
PriceSink, PriceSink,
NenjimApplication { NenjimProcess {
public TickerServiceImpl( public TickerServiceImpl(
@NotNull NenjimRegistryService registryService, @NotNull NenjimRegistryService registryService,
@NotNull NenjimComponentId... priceSourceIds) { @NotNull NenjimComponentId... priceSourceIds) {
@@ -4,7 +4,7 @@ import com.fanitas.evelyn.core.Evelyn;
import com.fanitas.evelyn.core.EvelynStatusIndexPoint; import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
import com.r35157.evelyn.emc.EvelynMissionControl; import com.r35157.evelyn.emc.EvelynMissionControl;
import com.r35157.libs.javafx.JavaFxRuntime; import com.r35157.libs.javafx.JavaFxRuntime;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.process.NenjimProcess;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
@@ -30,7 +30,7 @@ import java.util.function.Function;
public final class EvelynMissionControlImpl implements public final class EvelynMissionControlImpl implements
EvelynMissionControl, EvelynMissionControl,
NenjimApplication { NenjimProcess {
public EvelynMissionControlImpl( public EvelynMissionControlImpl(
@NotNull Evelyn evelynServiceProd, @NotNull Evelyn evelynServiceProd,
@@ -3,7 +3,7 @@ package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.cryptowallet.solana.SolanaWallet; import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm; import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.libs.jupiter.perps.JupiterPerpsService; import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.process.NenjimProcess;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.net.URI; import java.net.URI;
@@ -18,7 +18,7 @@ import java.util.concurrent.CountDownLatch;
public final class JupiterPerpsAlarmImpl implements public final class JupiterPerpsAlarmImpl implements
JupiterPerpsAlarm, JupiterPerpsAlarm,
NenjimApplication { NenjimProcess {
public JupiterPerpsAlarmImpl( public JupiterPerpsAlarmImpl(
@NotNull SolanaWallet solanaWallet, @NotNull SolanaWallet solanaWallet,
@@ -1,6 +0,0 @@
package com.r35157.nenjim.component;
/** A Nenjim component with an active start lifecycle. */
public interface NenjimApplication extends NenjimComponent {
void start() throws Exception;
}
@@ -1,7 +1,6 @@
package com.r35157.nenjim.hubd; package com.r35157.nenjim.hubd;
import com.r35157.nenjim.component.NenjimApplication; import com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl;
import com.r35157.nenjim.service.registry.impl.ref.NenjimRegistryServiceManagerImpl;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public final class Main { public final class Main {
@@ -9,8 +8,7 @@ public final class Main {
} }
public static void main(@NotNull String[] args) throws Exception { public static void main(@NotNull String[] args) throws Exception {
NenjimApplication registryServiceManager = NenjimHub nenjimHub = new NenjimHubImpl();
new NenjimRegistryServiceManagerImpl(); nenjimHub.start();
registryServiceManager.start();
} }
} }
@@ -0,0 +1,21 @@
package com.r35157.nenjim.hubd;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/** Global owner of one Nenjim runtime's composition and process lifecycle. */
public interface NenjimHub {
void start() throws Exception;
/**
* Returns an immutable point-in-time snapshot in successful startup order.
*
* <p>An ID is present only when that process's {@code start()} invocation
* returned successfully. Presence makes no health or continued-liveness
* guarantee.</p>
*/
@NotNull
List<NenjimComponentId> getStartedProcessIds();
}
@@ -0,0 +1,372 @@
package com.r35157.nenjim.hubd.impl.ref;
import com.fanitas.evelyn.core.Evelyn;
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.assetaz.services.cis.CurrencyTypeIds;
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded.HardcodedPriceSource;
import com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool.RaydiumPoolPriceSource;
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
import com.r35157.evelyn.emc.EvelynMissionControl;
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.jupiter.swap.JupiterSwapService;
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.nenjim.hubd.NenjimHub;
import com.r35157.nenjim.process.NenjimProcess;
import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
import com.r35157.nenjim.service.registry.impl.ref.NenjimRegistryAssembly;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import com.r35157.libs.raydium.Raydium;
import com.r35157.libs.raydium.impl.ref.RaydiumImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.service.notification.BoundNotificationService;
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
/** Reference composition root and runtime lifecycle owner. */
public final class NenjimHubImpl implements NenjimHub {
@Override
public void start() throws Exception {
if (!startAttempted.compareAndSet(false, true)) {
throw new IllegalStateException(
"NenjimHub start has already been attempted");
}
log.info("Initializing Nenjim Registry and components...");
NenjimRegistryAssembly registryAssembly =
NenjimRegistryAssembly.create();
NenjimRegistryService registryService =
registryAssembly.registryService();
NenjimProcess registryProcess = registryAssembly.registryProcess();
registryServiceManager = registryAssembly.registryServiceManager();
registryProcess.start();
registryServiceManager.registerComponent(
REGISTRY_SERVICE_ID,
registryService);
recordStartedProcess(REGISTRY_SERVICE_ID);
initializeAndRegisterComponents(
registryService,
registryServiceManager);
log.info("Starting explicitly enabled Nenjim processes...");
startProcess(registryService, TICKER_SERVICE_ID);
startProcess(registryService, EVELYN_SERVICE_PROD_ID);
startProcess(registryService, EVELYN_SERVICE_TEST_ID);
startProcess(registryService, EVELYN_IOU_BURNER_PROD_ID);
startProcess(registryService, EVELYN_MISSION_CONTROL_ID);
System.out.println("Done - Now online!");
waitForAndShutdown();
}
@Override
public synchronized @NotNull List<NenjimComponentId>
getStartedProcessIds() {
return List.copyOf(startedProcessIds);
}
private void initializeAndRegisterComponents(
NenjimRegistryService registryService,
NenjimRegistryServiceManager registryServiceManager) {
initializeBuildingBlocks(registryServiceManager);
initializePriceSources(registryServiceManager);
initializeServices(registryService, registryServiceManager);
initializeApplications(registryServiceManager);
}
private void initializeBuildingBlocks(
NenjimRegistryServiceManager registryServiceManager) {
objectCache = new ObjectCacheImpl();
registryServiceManager.registerComponent(OBJECT_CACHE_ID, objectCache);
clockUTC = Clock.systemUTC();
cis = new HardcodedCurrencyIdentityService();
registryServiceManager.registerComponent(CURRENCY_IDENTITY_SERVICE_ID, cis);
httpClient = HttpClient.newHttpClient();
objectMapper = new ObjectMapper();
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
registryServiceManager.registerComponent(SOLANA_BLOCKCHAIN_DIRECT_ID, solanaBlockChain);
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
registryServiceManager.registerComponent(SOLANA_BLOCKCHAIN_CACHED_ID, cachedSolanaBlockChain);
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
registryServiceManager.registerComponent(RAYDIUM_SERVICE_ID, raydium);
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
"evelyn_perps_test",
solanaBlockChain
);
registryServiceManager.registerComponent(
SOLANA_WALLET_EVELYN_PERPS_TEST_ID,
solanaWalletEvelynPerpsTest);
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
"evelyn_perps_prod",
solanaBlockChain
);
registryServiceManager.registerComponent(
SOLANA_WALLET_EVELYN_PERPS_PROD_ID,
solanaWalletEvelynPerpsProd);
solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
"5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
"evelyn_burner_prod",
solanaBlockChain
);
registryServiceManager.registerComponent(
SOLANA_WALLET_EVELYN_BURNER_PROD_ID,
solanaWalletEvelynBurnerProd);
}
private void initializePriceSources(
NenjimRegistryServiceManager registryServiceManager) {
hardcodedPriceSource = new HardcodedPriceSource(cis);
registryServiceManager.registerComponent(
HARDCODED_PRICE_SOURCE_ID,
hardcodedPriceSource);
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
registryServiceManager.registerComponent(
RAYDIUM_POOL_PRICE_SOURCE_ID,
raydiumPoolPriceSource);
}
private void initializeServices(
NenjimRegistryService registryService,
NenjimRegistryServiceManager registryServiceManager) {
tickerService = new TickerServiceImpl(
registryService,
RAYDIUM_POOL_PRICE_SOURCE_ID
);
registryServiceManager.registerComponent(TICKER_SERVICE_ID, tickerService);
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsProd,
cis
);
registryServiceManager.registerComponent(
JUPITER_PERPS_EVELYN_PROD_ID,
jupiterServicePerpsProd);
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsTest,
cis
);
registryServiceManager.registerComponent(
JUPITER_PERPS_EVELYN_TEST_ID,
jupiterServicePerpsTest);
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
solanaWalletEvelynPerpsTest,
jupiterServicePerpsTest,
"--config=conf/alarms.conf"
);
registryServiceManager.registerComponent(
JUPITER_PERPS_ALARM_ID,
jupiterPerpsAlarm);
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
registryServiceManager.registerComponent(EVELYN_SERVICE_PROD_ID, evelynServiceProd);
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
registryServiceManager.registerComponent(EVELYN_SERVICE_TEST_ID, evelynServiceTest);
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
registryServiceManager.registerComponent(
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
jupiterSwapService);
discordAssetAZTokenNotificationService = new DiscordNotificationServiceImpl(
URI.create("https://discordapp.com/api/webhooks/1537799046025187458/RtoNR5HnGmnutsMfM8AKP8mLDVqP1AxpEQ3p2IvBiaqVOjuVWNcShMkIh2D9uT27Mt4A"),
"DiscordBot (https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref, 0.1)",
Duration.ofSeconds(10)
);
registryServiceManager.registerComponent(
DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
discordAssetAZTokenNotificationService);
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
solanaWalletEvelynBurnerProd,
jupiterSwapService,
solanaBlockChain,
tickerService,
cis,
discordAssetAZTokenNotificationService,
burnTokenCandidates,
maximumSwapAmountPerIterationInUSD,
200,
2*1000*60L,
Duration.ofSeconds(10),
Duration.ofHours(1)
);
registryServiceManager.registerComponent(
EVELYN_IOU_BURNER_PROD_ID,
evelynIOUBurnerServiceProd);
}
private void initializeApplications(
NenjimRegistryServiceManager registryServiceManager) {
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
registryServiceManager.registerComponent(
EVELYN_MISSION_CONTROL_ID,
missionControl);
}
private void startProcess(
NenjimRegistryService registryService,
NenjimComponentId componentId) throws Exception {
NenjimProcess process = registryService.getComponent(
componentId,
NenjimProcess.class);
if (process == null) {
throw new IllegalStateException(
"Required Nenjim process '" + componentId.value()
+ "' is not registered as "
+ NenjimProcess.class.getName());
}
process.start();
recordStartedProcess(componentId);
}
private synchronized void recordStartedProcess(
NenjimComponentId componentId) {
startedProcessIds.add(componentId);
}
private void waitForAndShutdown() throws InterruptedException {
shutdownLatch.await();
System.out.println("Nenjim is now shut down!");
}
private static final Logger log = LoggerFactory.getLogger(
NenjimHubImpl.class);
private static final NenjimComponentId REGISTRY_SERVICE_ID =
new NenjimComponentId("nenjim.registry.service");
private static final NenjimComponentId OBJECT_CACHE_ID =
new NenjimComponentId("nenjim.object-cache.default");
private static final NenjimComponentId CURRENCY_IDENTITY_SERVICE_ID =
new NenjimComponentId("assetaz.currency-identity.hardcoded");
private static final NenjimComponentId SOLANA_BLOCKCHAIN_DIRECT_ID =
new NenjimComponentId("solana.blockchain.direct");
private static final NenjimComponentId SOLANA_BLOCKCHAIN_CACHED_ID =
new NenjimComponentId("solana.blockchain.cached");
private static final NenjimComponentId RAYDIUM_SERVICE_ID =
new NenjimComponentId("raydium.service.default");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_TEST_ID =
new NenjimComponentId("solana.wallet.evelyn-perps-test");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_PROD_ID =
new NenjimComponentId("solana.wallet.evelyn-perps-prod");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_BURNER_PROD_ID =
new NenjimComponentId("solana.wallet.evelyn-burner-prod");
private static final NenjimComponentId HARDCODED_PRICE_SOURCE_ID =
new NenjimComponentId("assetaz.price-source.hardcoded");
private static final NenjimComponentId RAYDIUM_POOL_PRICE_SOURCE_ID =
new NenjimComponentId(
"assetaz.price-source.raydium-pool.eve-usdt");
private static final NenjimComponentId TICKER_SERVICE_ID =
new NenjimComponentId("assetaz.ticker.default");
private static final NenjimComponentId JUPITER_PERPS_EVELYN_PROD_ID =
new NenjimComponentId("jupiter.perps.evelyn-prod");
private static final NenjimComponentId JUPITER_PERPS_EVELYN_TEST_ID =
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 JUPITER_SWAP_EVELYN_BURNER_PROD_ID =
new NenjimComponentId("jupiter.swap.evelyn-burner-prod");
private static final NenjimComponentId DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID =
new NenjimComponentId("notification.discord.assetaz-token");
private static final NenjimComponentId EVELYN_IOU_BURNER_PROD_ID =
new NenjimComponentId("evelyn.iou-burner.prod");
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
new NenjimComponentId("evelyn.mission-control.default");
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final AtomicBoolean startAttempted = new AtomicBoolean();
private final List<NenjimComponentId> startedProcessIds = new ArrayList<>();
private NenjimRegistryServiceManager registryServiceManager;
// Dependency components
private ObjectCache objectCache;
private Clock clockUTC;
private CurrencyIdentityService cis;
private SolanaBlockChain solanaBlockChain;
private SolanaBlockChain cachedSolanaBlockChain;
private Raydium raydium;
private TradingPair eveUSDT;
private HttpClient httpClient;
private ObjectMapper objectMapper;
private SolanaWallet solanaWalletEvelynPerpsProd;
private SolanaWallet solanaWalletEvelynPerpsTest;
private SolanaWallet solanaWalletEvelynBurnerProd;
private JupiterPerpsService jupiterServicePerpsProd;
private JupiterPerpsService jupiterServicePerpsTest;
// Price-source components
private PriceSource hardcodedPriceSource;
private PriceSource raydiumPoolPriceSource;
// Services
private TickerService tickerService;
private JupiterPerpsAlarm jupiterPerpsAlarm;
private Evelyn evelynServiceProd;
private Evelyn evelynServiceTest;
private JupiterSwapService jupiterSwapService;
private BoundNotificationService discordAssetAZTokenNotificationService;
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
// Application components
private EvelynMissionControl missionControl;
}
@@ -0,0 +1,8 @@
package com.r35157.nenjim.process;
import com.r35157.nenjim.component.NenjimComponent;
/** A Nenjim component with an active Hub-managed start lifecycle. */
public interface NenjimProcess extends NenjimComponent {
void start() throws Exception;
}
@@ -1,8 +1,12 @@
package com.r35157.nenjim.service.registry; package com.r35157.nenjim.service.registry;
import com.r35157.nenjim.component.NenjimComponent; import com.r35157.nenjim.component.NenjimComponent;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import org.jetbrains.annotations.NotNull;
/** Lifecycle owner for the Nenjim Registry and hardcoded component graph. */ /** Passive registration handle retained privately by the NenjimHub. */
public interface NenjimRegistryServiceManager extends NenjimComponent { public interface NenjimRegistryServiceManager {
void start() throws Exception; void registerComponent(
@NotNull NenjimComponentId componentId,
@NotNull NenjimComponent component);
} }
@@ -0,0 +1,48 @@
package com.r35157.nenjim.service.registry.impl.ref;
import com.r35157.nenjim.process.NenjimProcess;
import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
import org.jetbrains.annotations.NotNull;
/**
* Implementation-only construction boundary for one Registry subsystem.
*
* <p>This helper owns no runtime lifecycle and exposes no administrative
* Registry view. In particular, a consumer holding only
* {@link NenjimRegistryService} cannot use that service reference to derive
* the manager bound to the active Registry.</p>
*/
public final class NenjimRegistryAssembly {
private NenjimRegistryAssembly(
@NotNull NenjimRegistryServiceImpl registry) {
registryService = registry;
registryProcess = registry;
registryServiceManager = new NenjimRegistryServiceManagerImpl(
registry);
}
/** Creates mutually consistent public views of one new Registry. */
public static @NotNull NenjimRegistryAssembly create() {
return new NenjimRegistryAssembly(new NenjimRegistryServiceImpl());
}
/** Returns the ordinary read-only service view. */
public @NotNull NenjimRegistryService registryService() {
return registryService;
}
/** Returns the process view of the same object as the service view. */
public @NotNull NenjimProcess registryProcess() {
return registryProcess;
}
/** Returns the distinct passive manager bound to this Registry. */
public @NotNull NenjimRegistryServiceManager registryServiceManager() {
return registryServiceManager;
}
private final NenjimRegistryService registryService;
private final NenjimProcess registryProcess;
private final NenjimRegistryServiceManager registryServiceManager;
}
@@ -4,7 +4,16 @@ import com.r35157.nenjim.component.NenjimComponent;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId; import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** Internal mutation handle retained only by the Registry service manager. */ /**
* Internal mutation handle retained only by the Registry service manager.
*
* <p>The same Registry object is exposed publicly through
* {@link com.r35157.nenjim.service.registry.NenjimRegistryService}. This
* interface must remain package-private: making it public would allow ordinary
* consumers to cast that service object to the mutation interface and would
* break the Registry's read-only consumer contract. It must never be exposed
* outside this reference implementation package.</p>
*/
interface NenjimRegistryServiceAdmin { interface NenjimRegistryServiceAdmin {
void registerComponent( void registerComponent(
@NotNull NenjimComponentId componentId, @NotNull NenjimComponentId componentId,
@@ -1,7 +1,7 @@
package com.r35157.nenjim.service.registry.impl.ref; package com.r35157.nenjim.service.registry.impl.ref;
import com.r35157.nenjim.component.NenjimApplication;
import com.r35157.nenjim.component.NenjimComponent; import com.r35157.nenjim.component.NenjimComponent;
import com.r35157.nenjim.process.NenjimProcess;
import com.r35157.nenjim.service.registry.NenjimRegistryService; import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId; import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -19,7 +19,7 @@ import java.util.Set;
final class NenjimRegistryServiceImpl implements final class NenjimRegistryServiceImpl implements
NenjimRegistryService, NenjimRegistryService,
NenjimRegistryServiceAdmin, NenjimRegistryServiceAdmin,
NenjimApplication { NenjimProcess {
@Override @Override
public synchronized void start() { public synchronized void start() {
if (startAttempted) { if (startAttempted) {
@@ -1,351 +1,28 @@
package com.r35157.nenjim.service.registry.impl.ref; package com.r35157.nenjim.service.registry.impl.ref;
import com.fanitas.evelyn.core.Evelyn; import com.r35157.nenjim.component.NenjimComponent;
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.assetaz.services.cis.CurrencyTypeIds;
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded.HardcodedPriceSource;
import com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool.RaydiumPoolPriceSource;
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
import com.r35157.evelyn.emc.EvelynMissionControl;
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.jupiter.swap.JupiterSwapService;
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.nenjim.component.NenjimApplication;
import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager; import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId; import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import com.r35157.libs.raydium.Raydium; import org.jetbrains.annotations.NotNull;
import com.r35157.libs.raydium.impl.ref.RaydiumImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.service.notification.BoundNotificationService;
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.util.Objects;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Clock;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
/** Reference passive registration handle for one Registry object. */
public final class NenjimRegistryServiceManagerImpl implements public final class NenjimRegistryServiceManagerImpl implements
NenjimRegistryServiceManager, NenjimRegistryServiceManager {
NenjimApplication { NenjimRegistryServiceManagerImpl(
@NotNull NenjimRegistryServiceAdmin registryAdmin) {
this.registryAdmin = Objects.requireNonNull(
registryAdmin,
"registryAdmin");
}
@Override @Override
public void start() throws Exception { public void registerComponent(
if (!startAttempted.compareAndSet(false, true)) { @NotNull NenjimComponentId componentId,
throw new IllegalStateException( @NotNull NenjimComponent component) {
"Nenjim Registry service manager start has already been attempted"); registryAdmin.registerComponent(componentId, component);
} }
log.info("Initializing Nenjim Registry and components..."); private final NenjimRegistryServiceAdmin registryAdmin;
NenjimRegistryServiceImpl registry = new NenjimRegistryServiceImpl();
NenjimRegistryService registryService = registry;
NenjimRegistryServiceAdmin registryAdmin = registry;
NenjimApplication registryApplication = registry;
registryApplication.start();
registryAdmin.registerComponent(REGISTRY_SERVICE_ID, registry);
registryAdmin.registerComponent(REGISTRY_SERVICE_MANAGER_ID, this);
initializeAndRegisterComponents(registryService, registryAdmin);
log.info("Starting explicitly enabled Nenjim applications...");
startApplication(registryService, TICKER_SERVICE_ID);
startApplication(registryService, EVELYN_SERVICE_PROD_ID);
startApplication(registryService, EVELYN_SERVICE_TEST_ID);
startApplication(registryService, EVELYN_IOU_BURNER_PROD_ID);
startApplication(registryService, EVELYN_MISSION_CONTROL_ID);
System.out.println("Done - Now online!");
waitForAndShutdown();
}
private void initializeAndRegisterComponents(
NenjimRegistryService registryService,
NenjimRegistryServiceAdmin registryAdmin) {
initializeBuildingBlocks(registryAdmin);
initializePriceSources(registryAdmin);
initializeServices(registryService, registryAdmin);
initializeApplications(registryAdmin);
}
private void initializeBuildingBlocks(
NenjimRegistryServiceAdmin registryAdmin) {
objectCache = new ObjectCacheImpl();
registryAdmin.registerComponent(OBJECT_CACHE_ID, objectCache);
clockUTC = Clock.systemUTC();
cis = new HardcodedCurrencyIdentityService();
registryAdmin.registerComponent(CURRENCY_IDENTITY_SERVICE_ID, cis);
httpClient = HttpClient.newHttpClient();
objectMapper = new ObjectMapper();
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_DIRECT_ID, solanaBlockChain);
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_CACHED_ID, cachedSolanaBlockChain);
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
registryAdmin.registerComponent(RAYDIUM_SERVICE_ID, raydium);
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
"evelyn_perps_test",
solanaBlockChain
);
registryAdmin.registerComponent(
SOLANA_WALLET_EVELYN_PERPS_TEST_ID,
solanaWalletEvelynPerpsTest);
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
"evelyn_perps_prod",
solanaBlockChain
);
registryAdmin.registerComponent(
SOLANA_WALLET_EVELYN_PERPS_PROD_ID,
solanaWalletEvelynPerpsProd);
solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
"5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
"evelyn_burner_prod",
solanaBlockChain
);
registryAdmin.registerComponent(
SOLANA_WALLET_EVELYN_BURNER_PROD_ID,
solanaWalletEvelynBurnerProd);
}
private void initializePriceSources(
NenjimRegistryServiceAdmin registryAdmin) {
hardcodedPriceSource = new HardcodedPriceSource(cis);
registryAdmin.registerComponent(
HARDCODED_PRICE_SOURCE_ID,
hardcodedPriceSource);
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
registryAdmin.registerComponent(
RAYDIUM_POOL_PRICE_SOURCE_ID,
raydiumPoolPriceSource);
}
private void initializeServices(
NenjimRegistryService registryService,
NenjimRegistryServiceAdmin registryAdmin) {
tickerService = new TickerServiceImpl(
registryService,
RAYDIUM_POOL_PRICE_SOURCE_ID
);
registryAdmin.registerComponent(TICKER_SERVICE_ID, tickerService);
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsProd,
cis
);
registryAdmin.registerComponent(
JUPITER_PERPS_EVELYN_PROD_ID,
jupiterServicePerpsProd);
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsTest,
cis
);
registryAdmin.registerComponent(
JUPITER_PERPS_EVELYN_TEST_ID,
jupiterServicePerpsTest);
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
solanaWalletEvelynPerpsTest,
jupiterServicePerpsTest,
"--config=conf/alarms.conf"
);
registryAdmin.registerComponent(
JUPITER_PERPS_ALARM_ID,
jupiterPerpsAlarm);
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
registryAdmin.registerComponent(EVELYN_SERVICE_PROD_ID, evelynServiceProd);
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
registryAdmin.registerComponent(EVELYN_SERVICE_TEST_ID, evelynServiceTest);
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
registryAdmin.registerComponent(
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
jupiterSwapService);
discordAssetAZTokenNotificationService = new DiscordNotificationServiceImpl(
URI.create("https://discordapp.com/api/webhooks/1537799046025187458/RtoNR5HnGmnutsMfM8AKP8mLDVqP1AxpEQ3p2IvBiaqVOjuVWNcShMkIh2D9uT27Mt4A"),
"DiscordBot (https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref, 0.1)",
Duration.ofSeconds(10)
);
registryAdmin.registerComponent(
DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
discordAssetAZTokenNotificationService);
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
solanaWalletEvelynBurnerProd,
jupiterSwapService,
solanaBlockChain,
tickerService,
cis,
discordAssetAZTokenNotificationService,
burnTokenCandidates,
maximumSwapAmountPerIterationInUSD,
200,
2*1000*60L,
Duration.ofSeconds(10),
Duration.ofHours(1)
);
registryAdmin.registerComponent(
EVELYN_IOU_BURNER_PROD_ID,
evelynIOUBurnerServiceProd);
}
private void initializeApplications(
NenjimRegistryServiceAdmin registryAdmin) {
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
registryAdmin.registerComponent(
EVELYN_MISSION_CONTROL_ID,
missionControl);
}
private static void startApplication(
NenjimRegistryService registryService,
NenjimComponentId componentId) throws Exception {
NenjimApplication application = registryService.getComponent(
componentId,
NenjimApplication.class);
if (application == null) {
throw new IllegalStateException(
"Required Nenjim application '" + componentId.value()
+ "' is not registered as "
+ NenjimApplication.class.getName());
}
application.start();
}
private void waitForAndShutdown() throws InterruptedException {
shutdownLatch.await();
System.out.println("Nenjim is now shut down!");
}
private static final Logger log = LoggerFactory.getLogger(
NenjimRegistryServiceManagerImpl.class);
private static final NenjimComponentId REGISTRY_SERVICE_ID =
new NenjimComponentId("nenjim.registry.service");
private static final NenjimComponentId REGISTRY_SERVICE_MANAGER_ID =
new NenjimComponentId("nenjim.registry.service-manager");
private static final NenjimComponentId OBJECT_CACHE_ID =
new NenjimComponentId("nenjim.object-cache.default");
private static final NenjimComponentId CURRENCY_IDENTITY_SERVICE_ID =
new NenjimComponentId("assetaz.currency-identity.hardcoded");
private static final NenjimComponentId SOLANA_BLOCKCHAIN_DIRECT_ID =
new NenjimComponentId("solana.blockchain.direct");
private static final NenjimComponentId SOLANA_BLOCKCHAIN_CACHED_ID =
new NenjimComponentId("solana.blockchain.cached");
private static final NenjimComponentId RAYDIUM_SERVICE_ID =
new NenjimComponentId("raydium.service.default");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_TEST_ID =
new NenjimComponentId("solana.wallet.evelyn-perps-test");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_PROD_ID =
new NenjimComponentId("solana.wallet.evelyn-perps-prod");
private static final NenjimComponentId SOLANA_WALLET_EVELYN_BURNER_PROD_ID =
new NenjimComponentId("solana.wallet.evelyn-burner-prod");
private static final NenjimComponentId HARDCODED_PRICE_SOURCE_ID =
new NenjimComponentId("assetaz.price-source.hardcoded");
private static final NenjimComponentId RAYDIUM_POOL_PRICE_SOURCE_ID =
new NenjimComponentId(
"assetaz.price-source.raydium-pool.eve-usdt");
private static final NenjimComponentId TICKER_SERVICE_ID =
new NenjimComponentId("assetaz.ticker.default");
private static final NenjimComponentId JUPITER_PERPS_EVELYN_PROD_ID =
new NenjimComponentId("jupiter.perps.evelyn-prod");
private static final NenjimComponentId JUPITER_PERPS_EVELYN_TEST_ID =
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 JUPITER_SWAP_EVELYN_BURNER_PROD_ID =
new NenjimComponentId("jupiter.swap.evelyn-burner-prod");
private static final NenjimComponentId DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID =
new NenjimComponentId("notification.discord.assetaz-token");
private static final NenjimComponentId EVELYN_IOU_BURNER_PROD_ID =
new NenjimComponentId("evelyn.iou-burner.prod");
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
new NenjimComponentId("evelyn.mission-control.default");
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final AtomicBoolean startAttempted = new AtomicBoolean();
// Dependency components
private ObjectCache objectCache;
private Clock clockUTC;
private CurrencyIdentityService cis;
private SolanaBlockChain solanaBlockChain;
private SolanaBlockChain cachedSolanaBlockChain;
private Raydium raydium;
private TradingPair eveUSDT;
private HttpClient httpClient;
private ObjectMapper objectMapper;
private SolanaWallet solanaWalletEvelynPerpsProd;
private SolanaWallet solanaWalletEvelynPerpsTest;
private SolanaWallet solanaWalletEvelynBurnerProd;
private JupiterPerpsService jupiterServicePerpsProd;
private JupiterPerpsService jupiterServicePerpsTest;
// Price-source components
private PriceSource hardcodedPriceSource;
private PriceSource raydiumPoolPriceSource;
// Services
private TickerService tickerService;
private JupiterPerpsAlarm jupiterPerpsAlarm;
private Evelyn evelynServiceProd;
private Evelyn evelynServiceTest;
private JupiterSwapService jupiterSwapService;
private BoundNotificationService discordAssetAZTokenNotificationService;
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
// Application components
private EvelynMissionControl missionControl;
} }