76: Introduce the Nenjim component registry and move runtime composition into its manager
This commit is contained in:
+18
-15
@@ -4,10 +4,11 @@ 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.
|
||||
|
||||
The alarm is currently initialized and started by `NenjimHubImpl`. Its
|
||||
`SolanaWallet` and `JupiterPerpsService` are selected by the Hub and injected
|
||||
before startup. The alarm action configuration files do not select a wallet or
|
||||
signing key.
|
||||
The Registry service manager currently constructs and registers the alarm as
|
||||
`jupiter-perps-alarm.default`, but deliberately does not start it. Its
|
||||
`SolanaWallet` and `JupiterPerpsService` are selected by the manager's hardcoded
|
||||
composition and injected before registration. The alarm action configuration
|
||||
files do not select a wallet or signing key.
|
||||
|
||||
> **Important:** This application is not necessarily notification-only. Every active entry in a Jupiter Perps position action file can sign and submit a real transaction. Review both transaction action files before running it.
|
||||
|
||||
@@ -56,27 +57,29 @@ There is currently no alarm-specific automated self-test task.
|
||||
|
||||
## Running through Nenjim
|
||||
|
||||
The alarm no longer has a standalone `main` method. `NenjimHubImpl` constructs
|
||||
the wallet, Jupiter service and alarm, then starts the alarm as part of normal
|
||||
Hub startup. Run the configured application with:
|
||||
The alarm no longer has a standalone `main` method. The Registry service
|
||||
manager constructs and registers the wallet, Jupiter service and alarm, but the
|
||||
alarm is intentionally absent from the current explicit application startup
|
||||
list. Running the Hub therefore leaves the registered alarm inactive:
|
||||
|
||||
```bash
|
||||
./gradlew run
|
||||
```
|
||||
|
||||
After `./gradlew prepareLibs`, it can also be started explicitly with the Nenjim
|
||||
Hub main class:
|
||||
After `./gradlew prepareLibs`, the same Registry-managed Hub can be invoked with
|
||||
its main class (the alarm still remains inactive):
|
||||
|
||||
```bash
|
||||
java --enable-preview \
|
||||
-cp "build/libs/*" \
|
||||
com.r35157.nenjim.hubd.impl.ref.Main
|
||||
com.r35157.nenjim.hubd.Main
|
||||
```
|
||||
|
||||
The current Hub composition binds the alarm to `conf/alarms.conf` and to the
|
||||
The current manager composition binds the alarm to `conf/alarms.conf` and to the
|
||||
test Jupiter Perps wallet and service. Until Nenjim provides configured
|
||||
injection, a different configuration or wallet must be selected in the Hub's
|
||||
composition code, not in an alarm action file.
|
||||
injection and activation, a different configuration, wallet, or startup choice
|
||||
must be selected in the Registry manager's composition code, not in an alarm
|
||||
action file.
|
||||
|
||||
The repository contains example files for the alarm configuration and every
|
||||
action configuration. The real `conf/*.conf` files are ignored by Git; keep
|
||||
@@ -84,8 +87,8 @@ credentials and operational details out of version control.
|
||||
|
||||
### Environment variables
|
||||
|
||||
The Hub does not currently forward alarm-specific command-line options. Solana
|
||||
WebSocket endpoints can be overridden with:
|
||||
The Registry manager does not currently forward alarm-specific command-line
|
||||
options. Solana WebSocket endpoints can be overridden with:
|
||||
|
||||
```bash
|
||||
export SOLANA_WS_URLS='wss://first-provider.example,wss://second-provider.example'
|
||||
|
||||
+2
-1
@@ -11,7 +11,7 @@ if (version == "UNSET" && gradle.startParameter.taskNames.any { it.startsWith("p
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("com.r35157.nenjim.hubd.impl.ref.Main")
|
||||
mainClass.set("com.r35157.nenjim.hubd.Main")
|
||||
applicationDefaultJvmArgs = listOf("--enable-preview")
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies {
|
||||
implementation("org.slf4j:slf4j-api:2.0.18")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.13.4")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4")
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@@ -21,4 +21,4 @@ exec java \
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=*:${DEBUG_PORT}" \
|
||||
-Dlog4j.configurationFile=conf/log4j2.xml \
|
||||
-cp "$CLASSPATH" \
|
||||
com.r35157.nenjim.hubd.impl.ref.Main
|
||||
com.r35157.nenjim.hubd.Main
|
||||
|
||||
+31
-5
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nenjim Journal Documentation</title>
|
||||
<title>Nenjim Documentation</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #f8f9fa; color: #263238; margin: 0; }
|
||||
main { max-width: 980px; margin: auto; padding: 2rem; background: white; line-height: 1.55; }
|
||||
@@ -18,11 +18,12 @@
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Nenjim Journals</h1>
|
||||
<h1>Nenjim</h1>
|
||||
<p class="status"><strong>Implementation status:</strong> Journal format version 1, the immutable Java model,
|
||||
internal strict parser, read-only <code>JournalService</code>, and explicitly refreshed lifecycle
|
||||
<code>JournalServiceManager</code> are implemented. Contexts, resolution, classloading, downloading, publishing,
|
||||
synchronization, watching, signatures, and license filtering remain future work.</p>
|
||||
internal strict parser, read-only <code>JournalService</code>, explicitly refreshed lifecycle
|
||||
<code>JournalServiceManager</code>, and the first central component Registry are implemented. Contexts,
|
||||
resolution, classloading, discovery, runtime loading/removal, downloading, publishing, synchronization,
|
||||
watching, signatures, permissions, and license filtering remain future work.</p>
|
||||
|
||||
<h2>One Journal per artifact</h2>
|
||||
<p>A Journal describes exactly one artifact. API, test, and implementation artifacts therefore have independent
|
||||
@@ -116,6 +117,31 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
||||
<p>Only <code>.journal</code> files are runtime input. Version-controlled <code>.journal.example</code> files are
|
||||
documentation and are ignored by the service.</p>
|
||||
|
||||
<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
|
||||
as a service, application, algorithm, or adapter. <code>NenjimComponent</code> is therefore a pure marker,
|
||||
while <code>NenjimApplication</code> adds only a common <code>start()</code> operation and no common stop.</p>
|
||||
<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
|
||||
registration is indexed under every component interface implemented by that object, so its typed service and
|
||||
application views retain reference identity.</p>
|
||||
<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
|
||||
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
|
||||
Registry manager.</p>
|
||||
<p><code>NenjimRegistryServiceManagerImpl</code> starts and registers the Registry first, registers itself second,
|
||||
and then constructs and registers the hardcoded component graph in dependency order. Applications own their
|
||||
selection and activation: the Ticker explicitly selects the Raydium EVE/USDT source ID while the hardcoded
|
||||
source remains registered but inactive. The manager activates only the existing explicit five-application
|
||||
sequence; it does not start every <code>NenjimApplication</code>.
|
||||
<code>com.r35157.nenjim.hubd.Main</code> is only the outer Java bootstrap; <code>Main.main(...)</code> starts
|
||||
this manager and is not itself a component or application.</p>
|
||||
<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,
|
||||
persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, or automatic
|
||||
selection.</p>
|
||||
|
||||
<h2>Future Nenjim vision</h2>
|
||||
<p>Contexts, dependency-graph resolution, automatic selection/reconciliation, runtime upgrade or downgrade,
|
||||
classloaders, artifact downloads, IPFS retrieval, Maven dependencies, signatures, publishing, remote
|
||||
|
||||
+40
-7
@@ -1,8 +1,9 @@
|
||||
#<center><H1>Nenjim<br>Documentation</H1></center>
|
||||
|
||||
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
||||
> `JournalService` og den eksplicit refresh-baserede `JournalServiceManager` er implementeret. Afsnit 8–9 beskriver fremtidig vision og er ikke
|
||||
> funktionalitet i den nuværende Journal-implementation.
|
||||
> `JournalService`, den eksplicit refresh-baserede `JournalServiceManager` og første version af det centrale
|
||||
> component Registry er implementeret. Contexts, resolution, discovery, runtime loading og removal er fortsat
|
||||
> fremtidig funktionalitet.
|
||||
|
||||
**Indholdsfortegnelse:**
|
||||
<!-- TOC -->
|
||||
@@ -13,14 +14,15 @@
|
||||
* [5. Backing Store og Fleksibilitet](#5-backing-store-og-fleksibilitet)
|
||||
* [6. Standardiseret og Automatiseret Pakkehåndtering](#6-standardiseret-og-automatiseret-pakkehåndtering)
|
||||
* [7. Betydningen af Semantisk Versionering](#7-betydningen-af-semantisk-versionering)
|
||||
* [8. Integration af Plugins og Centralt Registry](#8-integration-af-plugins-og-centralt-registry)
|
||||
* [8. Componenter og det centrale Registry](#8-componenter-og-det-centrale-registry)
|
||||
* [9. Integration med AssetAZ og økonomisk aktivitet](#9-integration-med-assetaz-og-økonomisk-aktivitet)
|
||||
<!-- TOC -->
|
||||
|
||||
## 1. Introduktion til Nenjim
|
||||
Nenjim skal på sigt kunne håndtere forskellige versioner af softwareartifakter samtidigt. Det nu implementerede
|
||||
fundament er Journal-laget: et strengt tekstformat og en immutable model, som kan fastholde, hvad der var kendt om
|
||||
en artifakt på et bestemt tidspunkt. Dependency-resolution, Contexts, downloading og classloading kommer senere.
|
||||
en artifakt på et bestemt tidspunkt. Det første Registry kan desuden katalogisere de nuværende hardcodede
|
||||
componenter. Dependency-resolution, Contexts, downloading og classloading kommer senere.
|
||||
|
||||
## 2. Versionering og Afhængigheder
|
||||
Nenjim beskriver moduler, artifakter, releases, relationer og versionspolitik i Journaler. Almindelig Java-kode skal
|
||||
@@ -178,9 +180,40 @@ Format 1 accepterer kun stabile eksakte releases med alle tre numeriske komponen
|
||||
resolver altid vælger den numerisk højeste version: hard constraints afgrænser de gyldige valg, og policy/dependency
|
||||
hints kan på sigt foretrække en lavere kompatibel release.
|
||||
|
||||
## 8. Integration af Plugins og Centralt Registry
|
||||
Plugin discovery, et centralt registry, Journal publishing, remote synchronization og signature-validering er
|
||||
fremtidig vision. De er ikke implementeret af Journal-modellen, servicen eller service-manageren.
|
||||
## 8. Componenter og det centrale Registry
|
||||
|
||||
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
|
||||
fælles ord `component` og ikke `plugin`. `NenjimComponent` er en ren marker interface. En startbar component kan
|
||||
desuden implementere `NenjimApplication`, som kun lover `start()` og ikke en fælles stop-operation.
|
||||
|
||||
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
|
||||
`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
|
||||
application-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.
|
||||
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
|
||||
programming error. Public API'et kan ikke registrere eller fjerne componenter.
|
||||
|
||||
`NenjimRegistryServiceManagerImpl` konstruerer og starter Registry'et, registrerer Registry og manager først og
|
||||
konstruerer derefter den nuværende hardcodede component graph i dependency order. Den package-private admin-view er
|
||||
kun managerens registrationshandle og er hverken en component eller public API. 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
|
||||
`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
|
||||
Evelyn, Production IOU Burner og Mission Control. Alarmen og øvrige tidligere udkommenterede applikationer bliver
|
||||
ikke automatisk aktiveret. `com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap; `Main.main(...)` starter
|
||||
manageren gennem `NenjimApplication` og er ikke selv en component eller applikation.
|
||||
|
||||
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,
|
||||
automatic selection/start-all, usage tracking og permissions er fremtidigt arbejde. Journal publishing, remote
|
||||
synchronization og signature validation er ligeledes ikke implementeret.
|
||||
|
||||
## 9. Integration med AssetAZ og økonomisk aktivitet
|
||||
Økonomi, publicering og distribution er fremtidig vision. Format 1 beskriver alene artifaktviden; det indeholder
|
||||
|
||||
+13
-5
@@ -1,13 +1,21 @@
|
||||
# Tekniske termer
|
||||
|
||||
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service og lifecycle-manager er
|
||||
> implementeret. Resolver-, Context-, classloader-, retrieval- og publishing-termer beskriver kun fremtidig vision,
|
||||
> når det er angivet.
|
||||
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
||||
> lifecycle-manager og første version af det centrale component Registry er implementeret. Resolver-, Context-,
|
||||
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
||||
> det er angivet.
|
||||
|
||||
| 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. |
|
||||
| ServiceManager | Det offentlige lifecycle- og administrations-API, som en composition/lifecycle owner bruger til start, stop, configuration/data location, refresh og adgang til den tilhørende Service efter vellykket initialisering. Det er ikke det query-API, som almindelige consumers bruger. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| NenjimRegistryServiceAdmin | Package-private registrationshandle i referenceimplementationen. Det er ikke en component, er ikke indexeret og udleveres ikke til almindelige consumers. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| ArtifactCoordinate | Journal-kædens stabile identitet, udledt som `<GROUP>-<MODULE>-<ARTIFACT>`, f.eks. `com_r35157_nenjim-hubd-api`. Der findes intet separat `JournalId`. |
|
||||
| JournalVersion | Journal-snapshottets publiceringstid som UTC med millisekundpræcision: `uuuuMMddHHmmssSSS'Z'`. Den er forskellig fra en releases `PUBLISHED_AT`. |
|
||||
@@ -30,7 +38,7 @@
|
||||
| JournalService | Filesystem-uafhængigt, read-only Service-API for current og historical Journal-queries. Det eksponerer ingen lifecycle-, path-, refresh-, parser- eller mutationsoperationer. |
|
||||
| JournalServiceManager | Lifecycle- og administrations-API for JournalService. Det ejer den normaliserede data root, udfører komplet initial loading ved start, stopper servicen, udfører explicit refresh og udleverer JournalService efter vellykket initialisering. |
|
||||
| Journal referenceimplementation | `JournalServiceImpl`, `JournalServiceManagerImpl`, `JournalTextParser`, intern ingestion, chain state og filesystem-loading under `com.r35157.nenjim.service.journal.impl.ref`; implementation details lækker ikke gennem public signatures. |
|
||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg; ikke implementeret af denne Journal-change. |
|
||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||
|
||||
# Domænespecifikke termer
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-26
|
||||
@@ -0,0 +1,79 @@
|
||||
## Context
|
||||
|
||||
See `proposal.md` for motivation and the three delta specs for observable behavior. The current `NenjimHubImpl` constructor eagerly creates one hardcoded object graph, while `start()` activates five objects in a specific dependency-safe order and then waits forever on a latch. Component interfaces are unrelated Java interfaces, startable implementations do not share a lifecycle marker, and the Ticker still receives source instances through a temporary varargs constructor. Human-written Java remains in `.tjava`; Detag runs through the normal Gradle build.
|
||||
|
||||
The implementation must retain the current concrete production/test bindings and active subset, including financially relevant wallet/service choices and the deliberately inactive alarm. The Registry is only a catalogue of constructed objects. It must remain injectable and mutable after startup, but it must not grow into an authorization, Context, discovery, resolution, persistence, loading, removal, or lifecycle-ownership system.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Establish small extractable public component and Registry API packages with reference-only administration and storage.
|
||||
- Guarantee deterministic typed lookup, immutable snapshots, alias registrations, recursive interface indexing, and internally consistent concurrent registration/read behavior.
|
||||
- Make the Registry manager the only current composition root while preserving hardcoded choices, activation order, and blocking behavior.
|
||||
- Make component selection explicit where it belongs to an application/service, beginning with the Ticker's configured Raydium source ID.
|
||||
- Remove the duplicate Hub composition surface and directly affected plugin terminology without migrating unrelated prototypes.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- No general dependency-injection framework, reflection-based construction, start-all loop, lifecycle graph, rollback/stop orchestration, or manager getter for the Registry.
|
||||
- No redesign of existing domain lifecycle APIs or constructor wiring where the application does not own a selectable component choice.
|
||||
- No migration of legacy `crypto.r35157` prototypes, Context/runtime roadmap contracts, or Journal service startup.
|
||||
- No permanent automated tests or production-main validation.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Synchronized ordered storage provides the first Registry's consistency model
|
||||
|
||||
`NenjimRegistryServiceImpl` uses a `LinkedHashMap<NenjimComponentId, NenjimComponent>` as the primary catalogue and a map from component-interface token to ordered mutable ID lists as the secondary index. Registration and both queries synchronize on the Registry object. `getComponentIds(...)` returns `List.copyOf(...)`, so callers receive immutable point-in-time snapshots and later registration cannot mutate an earlier result.
|
||||
|
||||
This coarse lock is preferred to concurrent maps plus independently updated lists because registration must update both logical structures atomically and the hardcoded catalogue is small. Copy-on-write structures were considered, but would add more coordination without improving this startup-heavy workload.
|
||||
|
||||
### Interface discovery walks class and interface inheritance
|
||||
|
||||
Before mutating storage, registration walks the implementation class and its superclasses. For every declared interface, it recursively walks parent interfaces and collects each interface assignable to `NenjimComponent` in insertion-preserving set order. The marker itself is therefore indexed, inherited component interfaces are included, and duplicate inheritance paths add an ID only once. Concrete classes never become keys. `NenjimRegistryServiceAdmin` is naturally excluded because it intentionally does not extend the marker.
|
||||
|
||||
Reflection-time discovery is performed once per registration, not during each query. Scanning the primary map on lookup was rejected because issue #76 explicitly requires a secondary type index.
|
||||
|
||||
### Validation precedes lifecycle gating and mutation
|
||||
|
||||
Lookup arguments are null/type validated before checking started state, giving explicit API-contract failures for malformed calls. Valid queries then require successful Registry startup. Registration validates and computes all index keys before adding the primary record or any index entry; duplicate detection therefore leaves both structures unchanged.
|
||||
|
||||
The Registry and manager each retain a separate `startAttempted` flag set at the beginning of the synchronized first call. The Registry publishes `started` only after its start body succeeds. The manager has no restart recovery: any construction or activation failure leaves the attempt consumed, as required. This is separate from existing component-specific restart behavior.
|
||||
|
||||
### The manager composes through explicit IDs without exposing administration
|
||||
|
||||
The manager creates one Registry implementation and retains public service, package-private admin, and application views of that same object. It starts the application view, then registers the Registry once and the manager once before registering the remaining components in the issue's exact dependency order. Explicit `NenjimComponentId` constants name every binding.
|
||||
|
||||
Existing constructor dependencies remain ordinary interface-typed wiring owned by the composition root unless an application/service owns a configurable choice. The Ticker is the first such consumer: its public constructor receives only `NenjimRegistryService` plus explicit price-source IDs and resolves each ID as `PriceSource`. This preserves the selected Raydium source while leaving the registered hardcoded source inactive. Passing the admin view, selecting the first interface match, or adding automatic discovery was rejected because each would violate the ownership boundary.
|
||||
|
||||
### Application activation is an explicit ordered ID list
|
||||
|
||||
After all registrations, the manager resolves exactly the five enabled IDs through `NenjimApplication` and starts them in the existing order. This both exercises multi-interface indexing and prevents accidental activation of the Registry, manager, alarm, or dormant applications. Registration itself performs no lifecycle work. A generic query-and-start-all loop was rejected because not every registered application is enabled.
|
||||
|
||||
### The bootstrap is a class, not a service surface
|
||||
|
||||
The final `com.r35157.nenjim.hubd.Main` class is only the outer Java entry point. It constructs a public Registry manager through the `NenjimApplication` view and delegates once; it is neither a `NenjimComponent` nor a `NenjimApplication`. The former composition entry point `com.r35157.nenjim.hubd.impl.ref.Main` and its `NenjimHubImpl` composition implementation remain removed, so the new class does not reinstate that architecture or create a second composition path. Gradle points directly to `com.r35157.nenjim.hubd.Main`.
|
||||
|
||||
### Package and terminology migration remains narrow
|
||||
|
||||
Only `com.r35157.assetaz.services.ticker.plugins.pricesource` and its implementation subpackages move to `.ticker.pricesource`. The public `PriceSource` becomes a component query interface; `PriceSink` remains an incidental callback interface. Other legacy packages or types containing plugin terminology are untouched.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [The manager still hardcodes operational component choices] → This change deliberately centralizes rather than generalizes composition; configuration, Context, discovery, persistence, and runtime loading remain separate designs.
|
||||
- [One Registry lock serializes all reads with rare registrations] → The catalogue is small and read operations are short; the simple lock guarantees consistency and can be revisited only if profiling warrants it.
|
||||
- [A manager failure can leave already-started components active] → The required lifecycle has no common stop and explicitly forbids retry; preserve fail-fast behavior and do not invent rollback semantics.
|
||||
- [Adding `NenjimComponent` to cross-module interfaces is source-visible] → Limit the marker to the exact registered query interfaces and do not mark records, value objects, callback sinks, or incidental helpers.
|
||||
- [Moving composition code could accidentally expose embedded operational values] → Preserve values without including them in Registry diagnostics or documentation, and inspect diff output with sensitive values redacted.
|
||||
- [The public Ticker constructor changes incompatibly] → The old constructor was explicitly documented as temporary; replace its one production call and retain the package-private data-root seam used by existing verification.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add component/Registry contracts and the synchronized package-private Registry implementation.
|
||||
2. Mark the exact registered query interfaces and active implementations, then move Ticker price-source packages and replace Ticker source injection with Registry-plus-ID selection.
|
||||
3. Move the hardcoded construction graph into the Registry manager, register every required ID, activate only the explicit subset, and retain the online wait.
|
||||
4. Replace the old Hub API/implementation/main with the thin bootstrap and update Gradle and directly affected documentation.
|
||||
5. Validate OpenSpec, compile through Detag/Java, run temporary package-scoped Registry probes, inspect package references and the redacted complete diff, and audit every issue criterion.
|
||||
|
||||
Rollback before publication is normal source-control reversal of the uncommitted change. The implementation does not migrate persistent state or mutate external Registry state.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
## Why
|
||||
|
||||
Nenjim's current bootstrap directly constructs and starts a hardcoded object graph, leaving ordinary applications without a typed catalogue of independently registered components. A small read-only Registry and a dedicated Registry manager establish component identity, lookup, composition, and lifecycle ownership without prematurely introducing Contexts, resolution, discovery, or runtime loading.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `NenjimComponent` and `NenjimApplication` contracts plus a validated `NenjimComponentId` value type.
|
||||
- Add a read-only `NenjimRegistryService`, package-private administration view, and thread-safe reference implementation with ordered interface indexing and single-use startup.
|
||||
- Add `NenjimRegistryServiceManager` and its public reference implementation as the one composition root for the current hardcoded component set and explicit active application subset.
|
||||
- **BREAKING**: Replace the old Hub interface, `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` composition implementation, and `com.r35157.nenjim.hubd.impl.ref.Main` entry point with the thin `com.r35157.nenjim.hubd.Main` bootstrap, whose `Main.main(...)` only starts the Registry manager.
|
||||
- Make every registered query interface a Nenjim component and make each registered no-argument lifecycle implementation a Nenjim application without adding a common stop contract.
|
||||
- Replace the Ticker's temporary directly injected source objects with an injected public Registry view and explicitly configured source component IDs.
|
||||
- **BREAKING**: Rename the directly affected Ticker price-source packages from `.plugins.pricesource` to `.pricesource` and update their imports and terminology.
|
||||
- Document component identity, Registry views, application-owned selection/activation, manager-owned construction/registration, and the boundary around deferred runtime features.
|
||||
- Preserve the current concrete component choices, dependency-safe startup order, deliberately inactive components, and online wait behavior.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `nenjim-component-registry`: Defines component/application contracts, component IDs, read-only lookup, internal registration, interface indexing, lifecycle, hardcoded composition, and bootstrap behavior.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `assetaz-ticker-service`: Replaces temporary Context/plugin discovery language with explicit component-ID selection through the injected read-only Registry while preserving source lifecycle and history behavior.
|
||||
- `assetaz-currency-identity-service`: Transfers ownership of the one shared hardcoded catalogue from the removed Hub composition implementation to the Registry service manager.
|
||||
|
||||
## Impact
|
||||
|
||||
- Adds public component and Registry APIs under `com.r35157.nenjim.component` and `com.r35157.nenjim.service.registry`, with administration and storage confined to `.impl.ref`.
|
||||
- Moves current composition into `NenjimRegistryServiceManagerImpl`, changes the application main class, and removes the superseded Hub implementation path.
|
||||
- Updates the registered domain interfaces and lifecycle implementation declarations needed for typed Registry discovery.
|
||||
- Changes the public `TickerServiceImpl` construction contract and the Ticker price-source package names.
|
||||
- Updates Nenjim, terminology, alarm, and OpenSpec documentation; no new dependency, ValueTag, persistent state, runtime discovery, Context, resolution, classloading, permission, event, removal, or common-stop API is introduced.
|
||||
+16
@@ -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 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.
|
||||
|
||||
#### 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 Registry service manager 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
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
## 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 Registry manager 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
|
||||
|
||||
### Requirement: Ticker exposes typed latest prices
|
||||
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active Registry-resolved sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no resolved sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
|
||||
|
||||
#### Scenario: Latest supported price is available
|
||||
- **WHEN** a caller requests a trading pair after at least one observation has been loaded or persisted by an active source
|
||||
- **THEN** the ticker returns the successfully persisted observation with the greatest timestamp and its source identity
|
||||
|
||||
#### Scenario: Newest observation is selected across sources
|
||||
- **WHEN** multiple active sources for one trading pair have successfully persisted observations
|
||||
- **THEN** the ticker returns the observation with the greatest `observedAt` across those sources
|
||||
|
||||
#### Scenario: Pair has no resolved or active source
|
||||
- **WHEN** a caller requests a pair with no configured Registry-resolved source or whose resolved sources are all inactive
|
||||
- **THEN** the ticker throws an exception that clearly identifies the unavailable trading pair
|
||||
|
||||
#### Scenario: Active pair has no persisted observation
|
||||
- **WHEN** a caller requests a pair whose active sources have no successfully persisted observation
|
||||
- **THEN** the ticker throws an exception that clearly states that no persisted price is available for that trading pair
|
||||
|
||||
### Requirement: Price history explicitly activates each source
|
||||
For each configured Registry-resolved source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the resolved source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
|
||||
|
||||
#### Scenario: Existing source history activates and restores identity
|
||||
- **WHEN** a Registry-resolved source has an existing valid history with permitted whitespace or comments
|
||||
- **THEN** that source is active and its observations are restored with its captured trading pair and source name
|
||||
|
||||
#### Scenario: Empty source history activates without a price
|
||||
- **WHEN** a Registry-resolved source has an existing empty or comment-only history file
|
||||
- **THEN** that source is active without an initial observation
|
||||
|
||||
#### Scenario: Missing source history leaves only that source inactive
|
||||
- **WHEN** one resolved source history is missing while another source for the same pair has an existing history
|
||||
- **THEN** the missing source is not started, the existing source remains active, and no missing path is created
|
||||
|
||||
#### Scenario: Missing history warning identifies the source
|
||||
- **WHEN** a Registry-resolved source's expected history file is absent
|
||||
- **THEN** the ticker logs a warning containing its trading pair, source name, and complete expected UUID-based path
|
||||
|
||||
#### Scenario: Malformed source history is rejected clearly
|
||||
- **WHEN** comment text is removed and a non-empty data line in an active source history lacks a valid UTC timestamp and price
|
||||
- **THEN** ticker startup fails with an error containing the history filename, one-based physical line number, and identifying invalid content
|
||||
|
||||
#### Scenario: Legacy history is not migrated
|
||||
- **WHEN** only `data/assetaz/ticker/EVE_USDC.prices` exists
|
||||
- **THEN** the ticker does not read, move, copy, or create a replacement for that legacy file
|
||||
|
||||
### Requirement: History uses the human-editable observation format
|
||||
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the Registry-resolved source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
|
||||
|
||||
#### Scenario: Observation is written in the required format
|
||||
- **WHEN** any source observation at `2026-08-05T13:15:42.783Z` with price `14.85` is persisted
|
||||
- **THEN** the appended line is `20260805131542783Z:14.85`
|
||||
|
||||
#### Scenario: Identity is not duplicated in data lines
|
||||
- **WHEN** an observation is appended to a source history
|
||||
- **THEN** its data line contains neither source name nor trading-pair identity
|
||||
|
||||
#### Scenario: Existing operator annotations are preserved
|
||||
- **WHEN** the ticker appends an observation to a source history containing comments or blank lines
|
||||
- **THEN** those existing lines remain unchanged and the appended observation contains neither a comment nor extra annotation
|
||||
|
||||
### Requirement: New observations are durable before publication
|
||||
Only an active source resolved and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
|
||||
|
||||
#### Scenario: Active resolved source observation is persisted
|
||||
- **WHEN** an active source started by the ticker announces a valid price and timestamp
|
||||
- **THEN** the ticker appends and forces the observation to that source's history before considering it for latest
|
||||
|
||||
#### Scenario: Inactive or unknown source callback is rejected
|
||||
- **WHEN** an inactive source or a source not resolved by the ticker announces an observation
|
||||
- **THEN** the ticker rejects the callback without persisting or publishing it
|
||||
|
||||
#### Scenario: Future-dated observation remains latest
|
||||
- **WHEN** an accepted observation is persisted with a timestamp earlier than the current latest observation across active sources
|
||||
- **THEN** the accepted observation remains in its source history and the current later observation remains latest
|
||||
|
||||
#### Scenario: Concurrent source callbacks remain durable
|
||||
- **WHEN** active sources announce observations concurrently
|
||||
- **THEN** each successful append is a complete non-interleaved history line and latest identifies the greatest successfully persisted timestamp
|
||||
|
||||
#### Scenario: Persistence fails
|
||||
- **WHEN** appending or forcing an accepted observation fails
|
||||
- **THEN** the failure identifies the trading pair, source, and path, the attempted observation is not published, and the previously persisted latest observation remains available
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
## 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.
|
||||
|
||||
## ADDED 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
|
||||
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
|
||||
|
||||
#### Scenario: Surrounding whitespace is canonicalized
|
||||
- **WHEN** an ID is constructed from ` nenjim.registry.service `
|
||||
- **THEN** its stored value is `nenjim.registry.service`
|
||||
|
||||
#### Scenario: Valid component IDs are accepted
|
||||
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.service.prod` are constructed
|
||||
- **THEN** construction succeeds
|
||||
|
||||
#### Scenario: Invalid component IDs are rejected
|
||||
- **WHEN** an ID is null or contains uppercase letters, underscores, internal whitespace, an empty segment, a leading or trailing dot, or a malformed hyphen
|
||||
- **THEN** construction fails with the specified null or argument exception category and identifies the invalid canonicalized value without exposing secrets
|
||||
|
||||
### Requirement: Registry consumers receive only typed read-only lookup
|
||||
`NenjimRegistryService` SHALL be a Nenjim component and SHALL expose exactly an ordered-ID lookup by component interface and a nullable typed lookup by component ID and expected component interface. Both operations SHALL reject null arguments. A query type SHALL be an interface extending `NenjimComponent`; concrete classes and ordinary interfaces SHALL be rejected.
|
||||
|
||||
#### Scenario: List matching component IDs
|
||||
- **WHEN** a caller requests the IDs for a valid component interface
|
||||
- **THEN** the Registry returns all registrations indexed under that interface in registration order as an immutable snapshot that is not changed by later registrations
|
||||
|
||||
#### Scenario: No component implements an interface
|
||||
- **WHEN** a valid component interface has no indexed registration
|
||||
- **THEN** the Registry returns an empty immutable list rather than null
|
||||
|
||||
#### Scenario: Retrieve a matching component
|
||||
- **WHEN** an existing component ID is requested through an interface implemented by its object
|
||||
- **THEN** the Registry returns that object cast to the requested interface
|
||||
|
||||
#### Scenario: Component ID is missing
|
||||
- **WHEN** an unused component ID is requested through a valid component interface
|
||||
- **THEN** the Registry returns null
|
||||
|
||||
#### Scenario: Existing component has the wrong type
|
||||
- **WHEN** an existing component ID is requested through a component interface its object does not implement
|
||||
- **THEN** lookup fails with `IllegalArgumentException` identifying the ID, requested interface name, and actual implementation class name
|
||||
|
||||
#### Scenario: Query token is not a component interface
|
||||
- **WHEN** a caller supplies a concrete class, an ordinary interface, or null as a query token
|
||||
- **THEN** the Registry rejects it with the specified argument or null exception category
|
||||
|
||||
#### Scenario: Consumer cannot administer registrations
|
||||
- **WHEN** ordinary code receives `NenjimRegistryService`
|
||||
- **THEN** that public view exposes no registration, removal, loading, resolver, class-loading, stop, permission, event, or persistence operation
|
||||
|
||||
### Requirement: Internal registration maintains an interface index
|
||||
The internal Registry administration view SHALL remain package-private, SHALL not be a Nenjim component, and SHALL expose registration but no removal. Each registration SHALL atomically add a unique ID to a primary ID-to-object catalogue and to a secondary ordered index under every implemented interface in the recursive hierarchy that extends `NenjimComponent`. It SHALL not index concrete classes, ordinary interfaces, or the administration interface. Reads and registrations SHALL remain internally consistent when registrations occur after startup.
|
||||
|
||||
#### Scenario: Duplicate ID is rejected
|
||||
- **WHEN** a component is registered under an ID already present in the Registry
|
||||
- **THEN** registration fails with `IllegalArgumentException` identifying the duplicated ID and both the existing and attempted implementation class names without changing either index
|
||||
|
||||
#### Scenario: Null registration input is rejected
|
||||
- **WHEN** a registration supplies a null ID or component
|
||||
- **THEN** registration fails with `NullPointerException` without changing either index
|
||||
|
||||
#### Scenario: Same instance receives aliases
|
||||
- **WHEN** the same object is registered under multiple different unused IDs
|
||||
- **THEN** every registration succeeds and each ID resolves to the identical object reference
|
||||
|
||||
#### Scenario: Inherited component interfaces are indexed
|
||||
- **WHEN** a registered object implements a component interface that extends another component interface
|
||||
- **THEN** the registration ID appears under both interfaces, including inherited interfaces not directly declared by the implementation class
|
||||
|
||||
#### Scenario: One registration exposes multiple views
|
||||
- **WHEN** one object implements multiple component interfaces and is registered once
|
||||
- **THEN** its one ID appears under every implemented component interface and typed lookups preserve reference identity
|
||||
|
||||
#### Scenario: Registration occurs after startup
|
||||
- **WHEN** internal administration registers another component after the Registry has started
|
||||
- **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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: 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
|
||||
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.
|
||||
|
||||
#### Scenario: Ticker resolves its configured source
|
||||
- **WHEN** the Ticker is constructed with the public Registry and the Raydium EVE/USDT source ID
|
||||
- **THEN** it resolves that exact `PriceSource` and does not activate the hardcoded source
|
||||
|
||||
#### Scenario: Required configured dependency is missing
|
||||
- **WHEN** an application resolves a required hardcoded component ID that is absent
|
||||
- **THEN** construction or startup fails with a diagnostic identifying the required ID and interface without exposing component secrets
|
||||
|
||||
#### Scenario: Internal administration is not injected
|
||||
- **WHEN** an ordinary application receives its Registry dependency
|
||||
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
||||
|
||||
### 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.
|
||||
|
||||
#### Scenario: Start through the outer bootstrap
|
||||
- **WHEN** the configured Java entry point is invoked
|
||||
- **THEN** `Main.main(...)` constructs one Registry manager as a Nenjim application and delegates startup to it
|
||||
|
||||
#### Scenario: No duplicate composition path remains
|
||||
- **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
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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
|
||||
|
||||
### 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.
|
||||
|
||||
#### Scenario: Consumer inspects the first Registry API
|
||||
- **WHEN** a consumer examines the public Registry and manager contracts
|
||||
- **THEN** only the agreed read-only lookup and manager start operations are available and no deferred feature is exposed
|
||||
@@ -0,0 +1,25 @@
|
||||
## 1. Component and Registry Foundation
|
||||
|
||||
- [x] 1.1 Add the marker/application contracts, validated component ID, and public read-only Registry/service-manager interfaces with explicit nullability.
|
||||
- [x] 1.2 Implement the package-private Registry administration and synchronized Registry service with primary storage, recursive ordered interface indexing, typed lookup, immutable snapshots, and single-use startup.
|
||||
- [x] 1.3 Mark every required registered query interface as a Nenjim component and each registered no-argument lifecycle implementation as a Nenjim application without introducing a common stop API.
|
||||
|
||||
## 2. Composition and Migration
|
||||
|
||||
- [x] 2.1 Rename the directly affected Ticker price-source packages and imports from `.plugins.pricesource` to `.pricesource`.
|
||||
- [x] 2.2 Replace the Ticker's temporary source-instance constructor with public Registry injection plus explicit source component IDs and clear required-dependency failures.
|
||||
- [x] 2.3 Implement the public Registry service manager as the sole hardcoded composition root with every required component ID and implementation registered in dependency order.
|
||||
- [x] 2.4 Preserve the explicit five-application startup order, deliberately inactive components, online message, blocking wait, and single-use manager lifecycle.
|
||||
- [x] 2.5 Replace the old Hub interface and `com.r35157.nenjim.hubd.impl.ref.Main`/`NenjimHubImpl` composition path with the thin `com.r35157.nenjim.hubd.Main` bootstrap, update every runtime entry point, and keep composition and lifecycle ownership in the Registry manager.
|
||||
|
||||
## 3. Documentation and Specification Consistency
|
||||
|
||||
- [x] 3.1 Update Nenjim Markdown, terminology, and public HTML documentation for component identity, Registry views, composition ownership, selection/activation, and deferred boundaries.
|
||||
- [x] 3.2 Update directly affected alarm and canonical OpenSpec wording so removed Hub/plugin terminology does not describe current behavior.
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Strictly validate the OpenSpec change and complete project material while leaving the change active and unarchived.
|
||||
- [x] 4.2 Run the existing Gradle build and Detag/Java compilation pipeline without invoking production `main()`.
|
||||
- [x] 4.3 Run temporary non-committed Registry probes covering IDs, duplicate/alias behavior, multi-view and inherited indexing, immutable order snapshots, missing/wrong type, pre-start, repeated start, and post-start registration.
|
||||
- [x] 4.4 Inspect the complete redacted final diff, package references, generated-source status, nullability, working tree, acceptance criteria, and out-of-scope boundaries.
|
||||
@@ -67,7 +67,7 @@ The Currency Identity Service SHALL return all configured external references fo
|
||||
- **THEN** the result contains its configured Solana mint reference
|
||||
|
||||
### 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. NenjimHub SHALL create exactly one hardcoded service instance for its autorun context and SHALL pass that same instance to every autorun component 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 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.
|
||||
|
||||
#### Scenario: Conflicting external mapping
|
||||
- **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs
|
||||
@@ -77,9 +77,9 @@ The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ curr
|
||||
- **WHEN** initialization provides conflicting current values for the same UUID
|
||||
- **THEN** initialization fails with a clear conflict exception
|
||||
|
||||
#### Scenario: Compose autorun components
|
||||
- **WHEN** NenjimHub constructs its autorun component graph
|
||||
- **THEN** it creates one hardcoded Currency Identity Service and injects that same instance into every component that requires it
|
||||
#### Scenario: Compose hardcoded components
|
||||
- **WHEN** the Registry service manager 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
|
||||
|
||||
### Requirement: Trading pairs use service-owned identities
|
||||
Production code SHALL construct trading pairs from currency values obtained from the current Currency Identity Service and SHALL NOT maintain a parallel static registry of canonical currency or trading-pair instances.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource plugin contract.
|
||||
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource component contract.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -7,26 +7,30 @@ Provide AssetAZ callers with typed latest prices backed by explicitly enabled, d
|
||||
## Requirements
|
||||
|
||||
### Requirement: Price sources are discovered and lifecycle-managed
|
||||
Nenjim SHALL be able to construct each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL obtain `PriceSource` plugins from its Nenjim context, while ordinary ticker clients SHALL NOT receive a public operation for adding or managing sources. The ticker SHALL identify each obtained 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
|
||||
- **WHEN** Nenjim constructs a price source before constructing or starting the ticker
|
||||
- **WHEN** the Registry manager 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 obtains source plugins
|
||||
- **WHEN** the ticker initializes its source set from its Nenjim context
|
||||
- **THEN** it looks for implementations of `PriceSource`, not implementations of `PriceSink`, and manages the obtained sources internally
|
||||
#### 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: Ordinary clients cannot manage plugins
|
||||
#### 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 context sources are accepted
|
||||
- **WHEN** the ticker obtains two sources with different source names or trading pairs from its context
|
||||
#### 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 obtains sources with the same trading pair and source name
|
||||
- **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
|
||||
@@ -34,7 +38,7 @@ Nenjim SHALL be able to construct each price source independently in an initiali
|
||||
- **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 context-provided source
|
||||
- **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
|
||||
@@ -42,7 +46,7 @@ Nenjim SHALL be able to construct each price source independently in an initiali
|
||||
- **THEN** it rejects the call before replacing the sink from the successful start
|
||||
|
||||
### Requirement: Ticker exposes typed latest prices
|
||||
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active context-provided sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no obtained sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
|
||||
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active Registry-resolved sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no resolved sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
|
||||
|
||||
#### Scenario: Latest supported price is available
|
||||
- **WHEN** a caller requests a trading pair after at least one observation has been loaded or persisted by an active source
|
||||
@@ -52,8 +56,8 @@ The ticker SHALL expose the latest successfully persisted `PriceObservation` for
|
||||
- **WHEN** multiple active sources for one trading pair have successfully persisted observations
|
||||
- **THEN** the ticker returns the observation with the greatest `observedAt` across those sources
|
||||
|
||||
#### Scenario: Pair has no obtained or active source
|
||||
- **WHEN** a caller requests a pair with no context-provided source or whose obtained sources are all inactive
|
||||
#### Scenario: Pair has no resolved or active source
|
||||
- **WHEN** a caller requests a pair with no configured Registry-resolved source or whose resolved sources are all inactive
|
||||
- **THEN** the ticker throws an exception that clearly identifies the unavailable trading pair
|
||||
|
||||
#### Scenario: Active pair has no persisted observation
|
||||
@@ -61,22 +65,22 @@ The ticker SHALL expose the latest successfully persisted `PriceObservation` for
|
||||
- **THEN** the ticker throws an exception that clearly states that no persisted price is available for that trading pair
|
||||
|
||||
### Requirement: Price history explicitly activates each source
|
||||
For each context-provided source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the obtained source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
|
||||
For each configured Registry-resolved source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the resolved source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
|
||||
|
||||
#### Scenario: Existing source history activates and restores identity
|
||||
- **WHEN** a context-provided source has an existing valid history with permitted whitespace or comments
|
||||
- **WHEN** a Registry-resolved source has an existing valid history with permitted whitespace or comments
|
||||
- **THEN** that source is active and its observations are restored with its captured trading pair and source name
|
||||
|
||||
#### Scenario: Empty source history activates without a price
|
||||
- **WHEN** a context-provided source has an existing empty or comment-only history file
|
||||
- **WHEN** a Registry-resolved source has an existing empty or comment-only history file
|
||||
- **THEN** that source is active without an initial observation
|
||||
|
||||
#### Scenario: Missing source history leaves only that source inactive
|
||||
- **WHEN** one obtained source history is missing while another source for the same pair has an existing history
|
||||
- **WHEN** one resolved source history is missing while another source for the same pair has an existing history
|
||||
- **THEN** the missing source is not started, the existing source remains active, and no missing path is created
|
||||
|
||||
#### Scenario: Missing history warning identifies the source
|
||||
- **WHEN** a context-provided source's expected history file is absent
|
||||
- **WHEN** a Registry-resolved source's expected history file is absent
|
||||
- **THEN** the ticker logs a warning containing its trading pair, source name, and complete expected UUID-based path
|
||||
|
||||
#### Scenario: Malformed source history is rejected clearly
|
||||
@@ -88,7 +92,7 @@ For each context-provided source, the ticker SHALL derive an independent history
|
||||
- **THEN** the ticker does not read, move, copy, or create a replacement for that legacy file
|
||||
|
||||
### Requirement: History uses the human-editable observation format
|
||||
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the context-provided source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
|
||||
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the Registry-resolved source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
|
||||
|
||||
#### Scenario: Observation is written in the required format
|
||||
- **WHEN** any source observation at `2026-08-05T13:15:42.783Z` with price `14.85` is persisted
|
||||
@@ -103,14 +107,14 @@ Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, w
|
||||
- **THEN** those existing lines remain unchanged and the appended observation contains neither a comment nor extra annotation
|
||||
|
||||
### Requirement: New observations are durable before publication
|
||||
Only an active source obtained and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
|
||||
Only an active source resolved and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
|
||||
|
||||
#### Scenario: Active obtained source observation is persisted
|
||||
#### Scenario: Active resolved source observation is persisted
|
||||
- **WHEN** an active source started by the ticker announces a valid price and timestamp
|
||||
- **THEN** the ticker appends and forces the observation to that source's history before considering it for latest
|
||||
|
||||
#### Scenario: Inactive or unknown source callback is rejected
|
||||
- **WHEN** an inactive source or a source not obtained by the ticker announces an observation
|
||||
- **WHEN** an inactive source or a source not resolved by the ticker announces an observation
|
||||
- **THEN** the ticker rejects the callback without persisting or publishing it
|
||||
|
||||
#### Scenario: Future-dated observation remains latest
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# nenjim-component-registry Specification
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
|
||||
|
||||
#### Scenario: Surrounding whitespace is canonicalized
|
||||
- **WHEN** an ID is constructed from ` nenjim.registry.service `
|
||||
- **THEN** its stored value is `nenjim.registry.service`
|
||||
|
||||
#### Scenario: Valid component IDs are accepted
|
||||
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.service.prod` are constructed
|
||||
- **THEN** construction succeeds
|
||||
|
||||
#### Scenario: Invalid component IDs are rejected
|
||||
- **WHEN** an ID is null or contains uppercase letters, underscores, internal whitespace, an empty segment, a leading or trailing dot, or a malformed hyphen
|
||||
- **THEN** construction fails with the specified null or argument exception category and identifies the invalid canonicalized value without exposing secrets
|
||||
|
||||
### Requirement: Registry consumers receive only typed read-only lookup
|
||||
`NenjimRegistryService` SHALL be a Nenjim component and SHALL expose exactly an ordered-ID lookup by component interface and a nullable typed lookup by component ID and expected component interface. Both operations SHALL reject null arguments. A query type SHALL be an interface extending `NenjimComponent`; concrete classes and ordinary interfaces SHALL be rejected.
|
||||
|
||||
#### Scenario: List matching component IDs
|
||||
- **WHEN** a caller requests the IDs for a valid component interface
|
||||
- **THEN** the Registry returns all registrations indexed under that interface in registration order as an immutable snapshot that is not changed by later registrations
|
||||
|
||||
#### Scenario: No component implements an interface
|
||||
- **WHEN** a valid component interface has no indexed registration
|
||||
- **THEN** the Registry returns an empty immutable list rather than null
|
||||
|
||||
#### Scenario: Retrieve a matching component
|
||||
- **WHEN** an existing component ID is requested through an interface implemented by its object
|
||||
- **THEN** the Registry returns that object cast to the requested interface
|
||||
|
||||
#### Scenario: Component ID is missing
|
||||
- **WHEN** an unused component ID is requested through a valid component interface
|
||||
- **THEN** the Registry returns null
|
||||
|
||||
#### Scenario: Existing component has the wrong type
|
||||
- **WHEN** an existing component ID is requested through a component interface its object does not implement
|
||||
- **THEN** lookup fails with `IllegalArgumentException` identifying the ID, requested interface name, and actual implementation class name
|
||||
|
||||
#### Scenario: Query token is not a component interface
|
||||
- **WHEN** a caller supplies a concrete class, an ordinary interface, or null as a query token
|
||||
- **THEN** the Registry rejects it with the specified argument or null exception category
|
||||
|
||||
#### Scenario: Consumer cannot administer registrations
|
||||
- **WHEN** ordinary code receives `NenjimRegistryService`
|
||||
- **THEN** that public view exposes no registration, removal, loading, resolver, class-loading, stop, permission, event, or persistence operation
|
||||
|
||||
### Requirement: Internal registration maintains an interface index
|
||||
The internal Registry administration view SHALL remain package-private, SHALL not be a Nenjim component, and SHALL expose registration but no removal. Each registration SHALL atomically add a unique ID to a primary ID-to-object catalogue and to a secondary ordered index under every implemented interface in the recursive hierarchy that extends `NenjimComponent`. It SHALL not index concrete classes, ordinary interfaces, or the administration interface. Reads and registrations SHALL remain internally consistent when registrations occur after startup.
|
||||
|
||||
#### Scenario: Duplicate ID is rejected
|
||||
- **WHEN** a component is registered under an ID already present in the Registry
|
||||
- **THEN** registration fails with `IllegalArgumentException` identifying the duplicated ID and both the existing and attempted implementation class names without changing either index
|
||||
|
||||
#### Scenario: Null registration input is rejected
|
||||
- **WHEN** a registration supplies a null ID or component
|
||||
- **THEN** registration fails with `NullPointerException` without changing either index
|
||||
|
||||
#### Scenario: Same instance receives aliases
|
||||
- **WHEN** the same object is registered under multiple different unused IDs
|
||||
- **THEN** every registration succeeds and each ID resolves to the identical object reference
|
||||
|
||||
#### Scenario: Inherited component interfaces are indexed
|
||||
- **WHEN** a registered object implements a component interface that extends another component interface
|
||||
- **THEN** the registration ID appears under both interfaces, including inherited interfaces not directly declared by the implementation class
|
||||
|
||||
#### Scenario: One registration exposes multiple views
|
||||
- **WHEN** one object implements multiple component interfaces and is registered once
|
||||
- **THEN** its one ID appears under every implemented component interface and typed lookups preserve reference identity
|
||||
|
||||
#### Scenario: Registration occurs after startup
|
||||
- **WHEN** internal administration registers another component after the Registry has started
|
||||
- **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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: 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
|
||||
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.
|
||||
|
||||
#### Scenario: Ticker resolves its configured source
|
||||
- **WHEN** the Ticker is constructed with the public Registry and the Raydium EVE/USDT source ID
|
||||
- **THEN** it resolves that exact `PriceSource` and does not activate the hardcoded source
|
||||
|
||||
#### Scenario: Required configured dependency is missing
|
||||
- **WHEN** an application resolves a required hardcoded component ID that is absent
|
||||
- **THEN** construction or startup fails with a diagnostic identifying the required ID and interface without exposing component secrets
|
||||
|
||||
#### Scenario: Internal administration is not injected
|
||||
- **WHEN** an ordinary application receives its Registry dependency
|
||||
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
||||
|
||||
### 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.
|
||||
|
||||
#### Scenario: Start through the outer bootstrap
|
||||
- **WHEN** the configured Java entry point is invoked
|
||||
- **THEN** `Main.main(...)` constructs one Registry manager as a Nenjim application and delegates startup to it
|
||||
|
||||
#### Scenario: No duplicate composition path remains
|
||||
- **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
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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
|
||||
|
||||
### 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.
|
||||
|
||||
#### Scenario: Consumer inspects the first Registry API
|
||||
- **WHEN** a consumer examines the public Registry and manager contracts
|
||||
- **THEN** only the agreed read-only lookup and manager start operations are available and no deferred feature is exposed
|
||||
@@ -25,4 +25,4 @@ exec java \
|
||||
--enable-preview \
|
||||
"-Dlog4j.configurationFile=$LOG4J_CONFIG" \
|
||||
-cp "$CLASSPATH" \
|
||||
com.r35157.nenjim.hubd.impl.ref.Main
|
||||
com.r35157.nenjim.hubd.Main
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fanitas.evelyn.core;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@@ -7,7 +8,7 @@ import java.util.List;
|
||||
/**
|
||||
* Collects and exposes the status-index history for one named Evelyn instance.
|
||||
*/
|
||||
public interface Evelyn extends AutoCloseable {
|
||||
public interface Evelyn extends NenjimComponent, AutoCloseable {
|
||||
/**
|
||||
* Executes the legacy Evelyn service operation.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.assetaz.services.ticker.PriceObservation;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -45,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.TOKEN_2022_PROGRAM;
|
||||
|
||||
public class EvelynImpl implements Evelyn {
|
||||
public class EvelynImpl implements Evelyn, NenjimApplication {
|
||||
|
||||
public EvelynImpl(
|
||||
@NotNull String instanceName,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.fanitas.evelyn.service.burner;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -66,7 +67,7 @@ import java.util.List;
|
||||
* weaken duplicate prevention; operators should reconcile uncertain on-chain
|
||||
* activity before deliberately restarting a suspended or pending service.</p>
|
||||
*/
|
||||
public interface EvelynIOUBurnerService {
|
||||
public interface EvelynIOUBurnerService extends NenjimComponent {
|
||||
/**
|
||||
* Starts the dedicated worker and begins the first iteration immediately.
|
||||
*
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
@@ -48,7 +49,7 @@ import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_M
|
||||
* Reference implementation of the Evelyn IOU buyback-and-burn lifecycle.
|
||||
*/
|
||||
public final class EvelynIOUBurnerServiceImpl
|
||||
implements EvelynIOUBurnerService {
|
||||
implements EvelynIOUBurnerService, NenjimApplication {
|
||||
/**
|
||||
* Creates an Evelyn IOU burner with all runtime dependencies and current
|
||||
* operating policy.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.r35157.assetaz.services.cis;
|
||||
|
||||
import com.r35157.assetaz.valuetypes.CurrencyType;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -20,7 +21,7 @@ import java.util.UUID;
|
||||
* {@link CurrencyType} instances, but callers must compare currency values with
|
||||
* {@link Object#equals(Object)} rather than reference identity.</p>
|
||||
*/
|
||||
public interface CurrencyIdentityService {
|
||||
public interface CurrencyIdentityService extends NenjimComponent {
|
||||
/**
|
||||
* Resolves an AssetAZ currency UUID to its current currency metadata.
|
||||
*
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.r35157.assetaz.services.ticker;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides price observations for trading pairs.
|
||||
*/
|
||||
public interface TickerService {
|
||||
public interface TickerService extends NenjimComponent {
|
||||
|
||||
/**
|
||||
* Starts the ticker service.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.r35157.assetaz.services.ticker.impl.ref;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.PriceObservation;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -36,11 +39,28 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public final class TickerServiceImpl implements TickerService, PriceSink {
|
||||
public TickerServiceImpl(PriceSource... priceSources) {
|
||||
public final class TickerServiceImpl implements
|
||||
TickerService,
|
||||
PriceSink,
|
||||
NenjimApplication {
|
||||
public TickerServiceImpl(
|
||||
@NotNull NenjimRegistryService registryService,
|
||||
@NotNull NenjimComponentId... priceSourceIds) {
|
||||
this(DATA_ROOT);
|
||||
|
||||
for (PriceSource priceSource : priceSources) {
|
||||
Objects.requireNonNull(registryService, "registryService");
|
||||
Objects.requireNonNull(priceSourceIds, "priceSourceIds");
|
||||
for (NenjimComponentId priceSourceId : priceSourceIds) {
|
||||
Objects.requireNonNull(priceSourceId, "priceSourceId");
|
||||
PriceSource priceSource = registryService.getComponent(
|
||||
priceSourceId,
|
||||
PriceSource.class);
|
||||
if (priceSource == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Required Nenjim component '" + priceSourceId.value()
|
||||
+ "' is not registered as "
|
||||
+ PriceSource.class.getName());
|
||||
}
|
||||
addPriceSource(priceSource);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource;
|
||||
package com.r35157.assetaz.services.ticker.pricesource;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource;
|
||||
package com.r35157.assetaz.services.ticker.pricesource;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Obtains prices for one trading pair and announces them to a {@link PriceSink}.
|
||||
*/
|
||||
public interface PriceSource {
|
||||
public interface PriceSource extends NenjimComponent {
|
||||
|
||||
@NotNull TradingPair getTradingPair();
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded;
|
||||
package com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool;
|
||||
package com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.libs.raydium.Raydium;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
@@ -2,6 +2,7 @@ package com.r35157.cryptowallet.solana;
|
||||
|
||||
import com.r35157.libs.solana.SolanaSignedTransaction;
|
||||
import com.r35157.libs.solana.SolanaUnsignedTransaction;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -17,7 +18,7 @@ import java.math.BigDecimal;
|
||||
* <p>Signing and submission can be performed separately or as one complete
|
||||
* operation.</p>
|
||||
*/
|
||||
public interface SolanaWallet {
|
||||
public interface SolanaWallet extends NenjimComponent {
|
||||
/**
|
||||
* Returns the public Solana address controlled by this wallet.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.r35157.evelyn.emc;
|
||||
|
||||
public interface EvelynMissionControl {
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
public interface EvelynMissionControl extends NenjimComponent {
|
||||
void start();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
@@ -26,9 +28,14 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class EvelynMissionControlImpl implements EvelynMissionControl {
|
||||
public final class EvelynMissionControlImpl implements
|
||||
EvelynMissionControl,
|
||||
NenjimApplication {
|
||||
|
||||
public EvelynMissionControlImpl(Evelyn evelynServiceProd, Evelyn evelynServiceTest) {
|
||||
public EvelynMissionControlImpl(
|
||||
@NotNull Evelyn evelynServiceProd,
|
||||
@NotNull Evelyn evelynServiceTest
|
||||
) {
|
||||
this.evelynProd = Objects.requireNonNull(evelynServiceProd, "evelynServiceProd");
|
||||
this.evelynTest = Objects.requireNonNull(evelynServiceTest, "evelynServiceTest");
|
||||
refreshTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.r35157.jupiterperpsalarm;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
/**
|
||||
* Monitors configured Jupiter Perps prices and dispatches matching alarm
|
||||
* actions.
|
||||
@@ -9,7 +11,7 @@ package com.r35157.jupiterperpsalarm;
|
||||
* wallet is observed and used for transaction actions; alarm action files do
|
||||
* not select them.</p>
|
||||
*/
|
||||
public interface JupiterPerpsAlarm {
|
||||
public interface JupiterPerpsAlarm extends NenjimComponent {
|
||||
/**
|
||||
* Starts alarm monitoring asynchronously.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -15,7 +16,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
public final class JupiterPerpsAlarmImpl implements
|
||||
JupiterPerpsAlarm,
|
||||
NenjimApplication {
|
||||
|
||||
public JupiterPerpsAlarmImpl(
|
||||
@NotNull SolanaWallet solanaWallet,
|
||||
@@ -43,7 +46,7 @@ public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace(System.err);
|
||||
}
|
||||
}, "Nenjim Plugin - JupiterPerpsAlarm");
|
||||
}, "Nenjim Component - JupiterPerpsAlarm");
|
||||
this.thread.setDaemon(false);
|
||||
}
|
||||
|
||||
@@ -254,9 +257,10 @@ public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
|
||||
private void printUsage() {
|
||||
System.err.println("""
|
||||
JupiterPerpsAlarmImpl is constructed and started by NenjimHubImpl.
|
||||
JupiterPerpsAlarmImpl is constructed and registered by the Nenjim Registry service manager.
|
||||
It is intentionally excluded from the current explicit startup list.
|
||||
|
||||
Current Hub configuration:
|
||||
Current registered configuration:
|
||||
--config=conf/alarms.conf
|
||||
|
||||
Environment:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.r35157.libs.jupiter.perps;
|
||||
|
||||
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -16,7 +17,7 @@ import java.util.Set;
|
||||
* configured wallet is responsible for signing them. Position operations
|
||||
* return normally only after independent Solana {@code CONFIRMED} success.</p>
|
||||
*/
|
||||
public interface JupiterPerpsService {
|
||||
public interface JupiterPerpsService extends NenjimComponent {
|
||||
/**
|
||||
* Reads a Jupiter Perps position from a known position account.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.r35157.libs.jupiter.swap;
|
||||
|
||||
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -24,7 +25,7 @@ import java.math.BigDecimal;
|
||||
* endpoint, and independently awaits Solana confirmation. Legacy SPL-token
|
||||
* and Token-2022 behavior is unchanged.</p>
|
||||
*/
|
||||
public interface JupiterSwapService {
|
||||
public interface JupiterSwapService extends NenjimComponent {
|
||||
/**
|
||||
* Swaps an exact amount of a supported token or native SOL into an SPL
|
||||
* token.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.r35157.libs.objcache;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Stores objects under structured keys for a caller-supplied time to live.
|
||||
*/
|
||||
public interface ObjectCache {
|
||||
public interface ObjectCache extends NenjimComponent {
|
||||
|
||||
/**
|
||||
* Stores or replaces an object.
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.r35157.libs.raydium;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.Range;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Set;
|
||||
|
||||
public interface Raydium {
|
||||
public interface Raydium extends NenjimComponent {
|
||||
/**
|
||||
* Fetches the current price for a Raydium liquidity pool.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.r35157.libs.solana;
|
||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
@@ -21,7 +22,7 @@ import java.util.Set;
|
||||
* logic. Higher-level integrations are expected to interpret Solana accounts,
|
||||
* token holdings and derived addresses according to their own domain rules.</p>
|
||||
*/
|
||||
public interface SolanaBlockChain {
|
||||
public interface SolanaBlockChain extends NenjimComponent {
|
||||
/**
|
||||
* Fetches the native SOL balance for a Solana address.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.r35157.nenjim.component;
|
||||
|
||||
/** A Nenjim component with an active start lifecycle. */
|
||||
public interface NenjimApplication extends NenjimComponent {
|
||||
void start() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.r35157.nenjim.component;
|
||||
|
||||
/** Marker for a constructed Nenjim component that is ready for use. */
|
||||
public interface NenjimComponent {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.r35157.nenjim.hubd;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.impl.ref.NenjimRegistryServiceManagerImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class Main {
|
||||
private Main() {
|
||||
}
|
||||
|
||||
public static void main(@NotNull String[] args) throws Exception {
|
||||
NenjimApplication registryServiceManager =
|
||||
new NenjimRegistryServiceManagerImpl();
|
||||
registryServiceManager.start();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.r35157.nenjim.hubd;
|
||||
|
||||
import crypto.r35157.nenjim.NenjimProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Coordinates the lifecycle of Nenjim services and applications.
|
||||
*/
|
||||
public interface NenjimHub {
|
||||
/**
|
||||
* Starts all initialized services and applications in dependency order,
|
||||
* then blocks while the Hub remains online.
|
||||
*
|
||||
* @throws Exception if startup or shutdown waiting fails
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Requests startup of the implementation bound to a fully qualified process
|
||||
* interface name in the active context.
|
||||
*
|
||||
* <p>Dynamic binding through this method is not yet implemented by the
|
||||
* reference Hub.</p>
|
||||
*
|
||||
* @param fqInterfaceName fully qualified interface name; the interface must
|
||||
* extend {@link NenjimProcess}
|
||||
*/
|
||||
void startProcess(String fqInterfaceName);
|
||||
|
||||
/**
|
||||
* Provides a map of all running processes managed by the NenjimHub, keyed by the processId.
|
||||
*
|
||||
* @return a map of {@code NenjimProcess} running processes
|
||||
*/
|
||||
HashMap<Integer, NenjimProcess> getRunningProcesses();
|
||||
|
||||
/**
|
||||
* Performs no operation.
|
||||
*/
|
||||
void noop();
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.r35157.nenjim.hubd.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
|
||||
public class Main {
|
||||
|
||||
// TODO: Consider if we really need a Main class or we just need to move the main method to NenjimHubImpl?
|
||||
static void main() throws Exception {
|
||||
NenjimHub nenjimHub = new NenjimHubImpl();
|
||||
nenjimHub.start();
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
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.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded.HardcodedPriceSource;
|
||||
import com.r35157.assetaz.services.ticker.plugins.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.composer.NenjimComposer;
|
||||
import com.r35157.nenjim.composer.impl.ref.NenjimComposerImpl;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.npm.NenjimProcessManager;
|
||||
import com.r35157.nenjim.npm.impl.ref.NenjimProcessManagerImpl;
|
||||
import com.r35157.nenjim.ntt.NenjimTestTool;
|
||||
import com.r35157.nenjim.ntt.impl.ref.NenjimTestToolImpl;
|
||||
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 com.r35157.stm.SodaTaskManager;
|
||||
import com.r35157.stm.impl.ref.SodaTaskManagerImpl;
|
||||
import com.r35157.suwimo.hub.client.SuwimoClient;
|
||||
import com.r35157.suwimo.hub.client.impl.j2fx.SuwimoClientImpl;
|
||||
import crypto.r35157.nenjim.NenjimProcess;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
|
||||
|
||||
public class NenjimHubImpl implements NenjimHub {
|
||||
public NenjimHubImpl() {
|
||||
log.info("Initializing NenjimHub...");
|
||||
|
||||
initializeAll();
|
||||
|
||||
nextProcessId = 1;
|
||||
//processesScope = new StructuredTaskScope.ShutdownOnFailure();
|
||||
processes = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
|
||||
log.info("Starting:");
|
||||
log.info(" Services...");
|
||||
tickerService.start();
|
||||
//jupiterPerpsAlarm.start();
|
||||
evelynServiceProd.start();
|
||||
evelynServiceTest.start();
|
||||
evelynIOUBurnerServiceProd.start();
|
||||
|
||||
log.info(" Applications...");
|
||||
missionControl.start();
|
||||
//nenjimComposer.start();
|
||||
//processManager.start();
|
||||
//testTool.start();
|
||||
//sodaTaskManager.start();
|
||||
//suwimoClient.start();
|
||||
|
||||
System.out.println("Done - Now online!");
|
||||
|
||||
waitForAndShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, NenjimProcess> getRunningProcesses() {
|
||||
return processes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noop() {
|
||||
System.out.println("NenjimHub command: 'noop'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startProcess(String className) {
|
||||
ClassLoader loader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
/*processesScope.fork(() -> {
|
||||
Class<?> clazz = loader.loadClass(className);
|
||||
if (!NenjimProcess.class.isAssignableFrom(clazz)) {
|
||||
throw new IllegalArgumentException("'" + className + "' does NOT implement NenjimProcess");
|
||||
}
|
||||
|
||||
NenjimProcess proc = (NenjimProcess) clazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
processes.put(nextProcessId, proc);
|
||||
nextProcessId++;
|
||||
|
||||
NenjimContext context = new NenjimContext() { };
|
||||
proc.setNenjimProcessContext(context);
|
||||
proc.setNenjimHub(this);
|
||||
|
||||
Thread.currentThread().setName(proc.getProcessName());
|
||||
System.out.println(" Starter Nenjim process '" + proc.getProcessName() + "'");
|
||||
proc.run();
|
||||
|
||||
return null;
|
||||
});*/
|
||||
}
|
||||
|
||||
private void initializeAll() {
|
||||
log.info(" Initializing building blocks...");
|
||||
initializeBuildingBlocks();
|
||||
|
||||
log.info(" Initializing plugins...");
|
||||
initializePlugins();
|
||||
|
||||
log.info(" Initializing services...");
|
||||
initializeServices();
|
||||
|
||||
log.info(" Initializing applications...");
|
||||
initializeApplications();
|
||||
}
|
||||
|
||||
private void initializeBuildingBlocks() {
|
||||
objectCache = new ObjectCacheImpl();
|
||||
clockUTC = Clock.systemUTC();
|
||||
cis = new HardcodedCurrencyIdentityService();
|
||||
httpClient = HttpClient.newHttpClient();
|
||||
objectMapper = new ObjectMapper();
|
||||
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
|
||||
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
|
||||
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
|
||||
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
|
||||
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
|
||||
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
|
||||
"evelyn_perps_test",
|
||||
solanaBlockChain
|
||||
);
|
||||
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
|
||||
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
|
||||
"evelyn_perps_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
|
||||
"5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
|
||||
"evelyn_burner_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
}
|
||||
|
||||
private void initializePlugins() {
|
||||
hardcodedPriceSource = new HardcodedPriceSource(cis);
|
||||
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
|
||||
}
|
||||
|
||||
private void initializeServices() {
|
||||
tickerService = new TickerServiceImpl(
|
||||
//hardcodedPriceSource, // Activating this could be dangerous as other services rely on these prices
|
||||
raydiumPoolPriceSource
|
||||
);
|
||||
|
||||
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsProd,
|
||||
cis
|
||||
);
|
||||
|
||||
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsTest,
|
||||
cis
|
||||
);
|
||||
|
||||
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
|
||||
solanaWalletEvelynPerpsTest,
|
||||
jupiterServicePerpsTest,
|
||||
"--config=conf/alarms.conf"
|
||||
);
|
||||
|
||||
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
|
||||
|
||||
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
|
||||
|
||||
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
||||
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)
|
||||
);
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
// TODO: Old but more correct way to auto start plugins - but it is currently broken.
|
||||
/*
|
||||
String[] processesToAutoStart = {
|
||||
//"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
|
||||
//"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
|
||||
//"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
|
||||
//"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
|
||||
//"crypto.r35157.nenjim.SuwimoHub",
|
||||
//"crypto.r35157.nenjim.SodaTaskManager",
|
||||
//"crypto.r35157.assetaz.hub.AssetAZHub"
|
||||
};
|
||||
|
||||
for (String processInterfaceName : processesToAutoStart) {
|
||||
startProcess(processInterfaceName);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void initializeApplications() {
|
||||
//nenjimComposer = new NenjimComposerImpl();
|
||||
//processManager = new NenjimProcessManagerImpl();
|
||||
//testTool = new NenjimTestToolImpl();
|
||||
//sodaTaskManager = new SodaTaskManagerImpl();
|
||||
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
|
||||
//suwimoClient = new SuwimoClientImpl();
|
||||
}
|
||||
|
||||
private void waitForAndShutdown() throws InterruptedException {
|
||||
/*
|
||||
try {
|
||||
processesScope.join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
processesScope.close();
|
||||
*/
|
||||
|
||||
shutdownLatch.await();
|
||||
|
||||
System.out.println("Nenjim is now shutdown (stopped all processes)!");
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class);
|
||||
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
|
||||
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
private HashMap<Integer, NenjimProcess> processes;
|
||||
//private StructuredTaskScope.ShutdownOnFailure processesScope;
|
||||
private int nextProcessId;
|
||||
|
||||
// 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 evelynPerpsProd;
|
||||
private SolanaWallet solanaWalletEvelynPerpsProd;
|
||||
private SolanaWallet solanaWalletEvelynPerpsTest;
|
||||
private SolanaWallet solanaWalletEvelynBurnerProd;
|
||||
private JupiterPerpsService jupiterServicePerpsProd;
|
||||
private JupiterPerpsService jupiterServicePerpsTest;
|
||||
|
||||
// Plugins
|
||||
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;
|
||||
|
||||
// Applications
|
||||
private EvelynMissionControl missionControl;
|
||||
private NenjimComposer nenjimComposer;
|
||||
private NenjimProcessManager processManager;
|
||||
private NenjimTestTool testTool;
|
||||
private SodaTaskManager sodaTaskManager;
|
||||
private SuwimoClient suwimoClient;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.r35157.nenjim.kicker;
|
||||
|
||||
//import com.r35157.nenjim.hubd.Context;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import crypto.r35157.nenjim.NenjimHub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface NenjimProcess {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.r35157.nenjim.kicker.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.hubd.ctx.Context;
|
||||
import com.r35157.nenjim.kicker.NenjimKicker;
|
||||
|
||||
@@ -34,7 +33,6 @@ public final class Initializer {
|
||||
private void startNenjimKicker(Path pathToClassCache) throws Exception {
|
||||
System.out.println("Initializing NenjimKicker...");
|
||||
|
||||
NenjimHub nenjimHub = null;
|
||||
Context defaultContext = new Context();
|
||||
NenjimClassLoader nenjimClassLoader = new NenjimClassLoader(defaultContext, pathToClassCache);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.r35157.nenjim.kicker.impl.ref;
|
||||
|
||||
//import com.r35157.nenjim.hubd.Context;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.kicker.NenjimProcess;
|
||||
import crypto.r35157.nenjim.NenjimHub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class NenjimProcessImpl implements NenjimProcess {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.r35157.nenjim.service.registry;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Read-only catalogue of constructed Nenjim components. */
|
||||
public interface NenjimRegistryService extends NenjimComponent {
|
||||
@NotNull
|
||||
List<NenjimComponentId> getComponentIds(
|
||||
@NotNull Class<? extends NenjimComponent> componentInterface);
|
||||
|
||||
@Nullable
|
||||
<T extends NenjimComponent> T getComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull Class<T> expectedInterface);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.r35157.nenjim.service.registry;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
/** Lifecycle owner for the Nenjim Registry and hardcoded component graph. */
|
||||
public interface NenjimRegistryServiceManager extends NenjimComponent {
|
||||
void start() throws Exception;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.r35157.nenjim.service.registry.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Internal mutation handle retained only by the Registry service manager. */
|
||||
interface NenjimRegistryServiceAdmin {
|
||||
void registerComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull NenjimComponent component);
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.r35157.nenjim.service.registry.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/** Reference Registry storage with read-only public and internal admin views. */
|
||||
final class NenjimRegistryServiceImpl implements
|
||||
NenjimRegistryService,
|
||||
NenjimRegistryServiceAdmin,
|
||||
NenjimApplication {
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
if (startAttempted) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service start has already been attempted");
|
||||
}
|
||||
|
||||
startAttempted = true;
|
||||
started = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void registerComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull NenjimComponent component) {
|
||||
Objects.requireNonNull(componentId, "componentId");
|
||||
Objects.requireNonNull(component, "component");
|
||||
|
||||
NenjimComponent existingComponent = componentsById.get(componentId);
|
||||
if (existingComponent != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicate Nenjim component ID '" + componentId.value()
|
||||
+ "': existing implementation "
|
||||
+ existingComponent.getClass().getName()
|
||||
+ ", attempted implementation "
|
||||
+ component.getClass().getName());
|
||||
}
|
||||
|
||||
Set<Class<? extends NenjimComponent>> componentInterfaces =
|
||||
findComponentInterfaces(component.getClass());
|
||||
|
||||
componentsById.put(componentId, component);
|
||||
for (Class<? extends NenjimComponent> componentInterface
|
||||
: componentInterfaces) {
|
||||
componentIdsByInterface
|
||||
.computeIfAbsent(
|
||||
componentInterface,
|
||||
ignored -> new ArrayList<>())
|
||||
.add(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @NotNull List<NenjimComponentId> getComponentIds(
|
||||
@NotNull Class<? extends NenjimComponent> componentInterface) {
|
||||
validateComponentInterface(componentInterface);
|
||||
requireStarted();
|
||||
|
||||
List<NenjimComponentId> componentIds =
|
||||
componentIdsByInterface.get(componentInterface);
|
||||
return componentIds == null ? List.of() : List.copyOf(componentIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @Nullable <T extends NenjimComponent> T getComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull Class<T> expectedInterface) {
|
||||
Objects.requireNonNull(componentId, "componentId");
|
||||
validateComponentInterface(expectedInterface);
|
||||
requireStarted();
|
||||
|
||||
NenjimComponent component = componentsById.get(componentId);
|
||||
if (component == null) {
|
||||
return null;
|
||||
}
|
||||
if (!expectedInterface.isInstance(component)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Nenjim component '" + componentId.value()
|
||||
+ "' was requested as " + expectedInterface.getName()
|
||||
+ " but is implemented by "
|
||||
+ component.getClass().getName());
|
||||
}
|
||||
return expectedInterface.cast(component);
|
||||
}
|
||||
|
||||
private void requireStarted() {
|
||||
if (!started) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service has not started successfully");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateComponentInterface(@NotNull Class<?> type) {
|
||||
Objects.requireNonNull(type, "componentInterface");
|
||||
if (!type.isInterface()
|
||||
|| !NenjimComponent.class.isAssignableFrom(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected an interface extending "
|
||||
+ NenjimComponent.class.getName()
|
||||
+ ", but received " + type.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull Set<Class<? extends NenjimComponent>>
|
||||
findComponentInterfaces(@NotNull Class<?> implementationClass) {
|
||||
LinkedHashSet<Class<? extends NenjimComponent>> componentInterfaces =
|
||||
new LinkedHashSet<>();
|
||||
|
||||
for (Class<?> currentClass = implementationClass;
|
||||
currentClass != null;
|
||||
currentClass = currentClass.getSuperclass()) {
|
||||
for (Class<?> implementedInterface : currentClass.getInterfaces()) {
|
||||
collectComponentInterfaces(
|
||||
implementedInterface,
|
||||
componentInterfaces);
|
||||
}
|
||||
}
|
||||
return componentInterfaces;
|
||||
}
|
||||
|
||||
private static void collectComponentInterfaces(
|
||||
@NotNull Class<?> interfaceType,
|
||||
@NotNull Set<Class<? extends NenjimComponent>> destination) {
|
||||
if (NenjimComponent.class.isAssignableFrom(interfaceType)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends NenjimComponent> componentInterface =
|
||||
(Class<? extends NenjimComponent>) interfaceType;
|
||||
destination.add(componentInterface);
|
||||
}
|
||||
|
||||
for (Class<?> parentInterface : interfaceType.getInterfaces()) {
|
||||
collectComponentInterfaces(parentInterface, destination);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<NenjimComponentId, NenjimComponent> componentsById =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends NenjimComponent>, List<NenjimComponentId>>
|
||||
componentIdsByInterface = new LinkedHashMap<>();
|
||||
private boolean startAttempted;
|
||||
private boolean started;
|
||||
}
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
package com.r35157.nenjim.service.registry.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.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
|
||||
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.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.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;
|
||||
|
||||
public final class NenjimRegistryServiceManagerImpl implements
|
||||
NenjimRegistryServiceManager,
|
||||
NenjimApplication {
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
if (!startAttempted.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service manager start has already been attempted");
|
||||
}
|
||||
|
||||
log.info("Initializing Nenjim Registry and components...");
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.r35157.nenjim.service.registry.valuetypes;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Lookup identity assigned to one component registration in a Registry. */
|
||||
public record NenjimComponentId(@NotNull String value) {
|
||||
public NenjimComponentId {
|
||||
value = value.strip();
|
||||
|
||||
if (!value.matches(
|
||||
"[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:\\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*)*")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid Nenjim component ID: '" + value + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.r35157.service.notification;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -12,7 +13,7 @@ import java.io.IOException;
|
||||
* or how its destination is configured.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BoundNotificationService {
|
||||
public interface BoundNotificationService extends NenjimComponent {
|
||||
/**
|
||||
* Sends a notification message.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user