81: Control Nenjim process construction and ordered startup through NenjimHub.conf
This commit is contained in:
@@ -17,7 +17,7 @@ Every application-owned, human-maintained runtime configuration file starts
|
|||||||
with an explicit format version:
|
with an explicit format version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=<version supported by that file's loader>
|
||||||
```
|
```
|
||||||
|
|
||||||
Blank lines and full-line comments may precede the declaration, but it must be
|
Blank lines and full-line comments may precede the declaration, but it must be
|
||||||
@@ -33,6 +33,7 @@ configuration files. Compatible additions do not require a version increment.
|
|||||||
|
|
||||||
This policy currently applies to:
|
This policy currently applies to:
|
||||||
|
|
||||||
|
- `NenjimHub.conf` (mandatory version 1 with ordered `[AUTO_START]`)
|
||||||
- `alarms.conf`
|
- `alarms.conf`
|
||||||
- `alarmaction_Console.conf`
|
- `alarmaction_Console.conf`
|
||||||
- `alarmaction_Pushover.conf`
|
- `alarmaction_Pushover.conf`
|
||||||
@@ -44,6 +45,10 @@ This policy currently applies to:
|
|||||||
Third-party configuration formats such as Log4j's XML configuration are
|
Third-party configuration formats such as Log4j's XML configuration are
|
||||||
versioned by their owning library and do not use this declaration.
|
versioned by their owning library and do not use this declaration.
|
||||||
|
|
||||||
|
Nenjim Hub reads only `conf/NenjimHub.conf`; copy
|
||||||
|
`conf/NenjimHub.conf.example` before first use and edit the ordered process
|
||||||
|
list for the deployment role. The example is never loaded as runtime input.
|
||||||
|
|
||||||
## Release log:
|
## Release log:
|
||||||
| Date | Version | Description |
|
| Date | Version | Description |
|
||||||
|:-----------------|:--------------:|----------------------|
|
|:-----------------|:--------------:|----------------------|
|
||||||
|
|||||||
+25
-18
@@ -4,11 +4,12 @@ The Jupiter Perps Price Alarm monitors the Jupiter Perps aggregated oracle accou
|
|||||||
|
|
||||||
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
|
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
|
||||||
|
|
||||||
The NenjimHub currently constructs and registers the alarm as
|
NenjimHub knows how to construct the alarm as
|
||||||
`jupiter-perps-alarm.default`, but deliberately does not start it. Its
|
`jupiter-perps-alarm.default`, but does so only when that ID is explicitly
|
||||||
`SolanaWallet` and `JupiterPerpsService` are selected by the Hub's hardcoded
|
listed in `conf/NenjimHub.conf` under `[AUTO_START]`. Its passive
|
||||||
composition and injected before registration. The alarm action configuration
|
`SolanaWallet` and `JupiterPerpsService` dependencies are selected by the Hub
|
||||||
files do not select a wallet or signing key.
|
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.
|
> **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.
|
||||||
|
|
||||||
@@ -57,17 +58,23 @@ There is currently no alarm-specific automated self-test task.
|
|||||||
|
|
||||||
## Running through Nenjim
|
## Running through Nenjim
|
||||||
|
|
||||||
The alarm no longer has a standalone `main` method. The Registry service
|
The alarm no longer has a standalone `main` method. Its default Hub template
|
||||||
manager constructs and registers the wallet, Jupiter service and alarm, but the
|
intentionally omits the alarm, so it is neither constructed, registered nor
|
||||||
alarm is intentionally absent from the current explicit application startup
|
started unless the runtime `[AUTO_START]` section contains:
|
||||||
list. Running the Hub therefore leaves the registered alarm inactive:
|
|
||||||
|
```text
|
||||||
|
jupiter-perps-alarm.default
|
||||||
|
```
|
||||||
|
|
||||||
|
Registry and Journal must remain the first two configured processes. The
|
||||||
|
alarm's current wallet and Jupiter service dependencies are passive, so they
|
||||||
|
are available after Registry bootstrap. Run the configured Hub with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew run
|
./gradlew run
|
||||||
```
|
```
|
||||||
|
|
||||||
After `./gradlew prepareLibs`, the same Registry-managed Hub can be invoked with
|
After `./gradlew prepareLibs`, the same Hub can be invoked with its main class:
|
||||||
its main class (the alarm still remains inactive):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java --enable-preview \
|
java --enable-preview \
|
||||||
@@ -75,11 +82,11 @@ java --enable-preview \
|
|||||||
com.r35157.nenjim.hubd.Main
|
com.r35157.nenjim.hubd.Main
|
||||||
```
|
```
|
||||||
|
|
||||||
The current manager composition binds the alarm to `conf/alarms.conf` and to the
|
The current Hub constructor binds the selected alarm to `conf/alarms.conf` and
|
||||||
test Jupiter Perps wallet and service. Until Nenjim provides configured
|
to the test Jupiter Perps wallet and service. `[AUTO_START]` controls only
|
||||||
injection and activation, a different configuration, wallet, or startup choice
|
whether and when the known process is constructed; a different alarm path,
|
||||||
must be selected in the Registry manager's composition code, not in an alarm
|
wallet, or service still requires composition-code changes rather than an alarm
|
||||||
action file.
|
action-file setting.
|
||||||
|
|
||||||
The repository contains example files for the alarm configuration and every
|
The repository contains example files for the alarm configuration and every
|
||||||
action configuration. The real `conf/*.conf` files are ignored by Git; keep
|
action configuration. The real `conf/*.conf` files are ignored by Git; keep
|
||||||
@@ -87,8 +94,8 @@ credentials and operational details out of version control.
|
|||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
The Registry manager does not currently forward alarm-specific command-line
|
The Hub does not currently forward alarm-specific command-line options. Solana
|
||||||
options. Solana WebSocket endpoints can be overridden with:
|
WebSocket endpoints can be overridden with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export SOLANA_WS_URLS='wss://first-provider.example,wss://second-provider.example'
|
export SOLANA_WS_URLS='wss://first-provider.example,wss://second-provider.example'
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copy this template to conf/NenjimHub.conf before starting Nenjim Hub.
|
||||||
|
# Registry and Journal are mandatory and must be the first two entries.
|
||||||
|
# Entries are ordered. List every process dependency manually before its consumers.
|
||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
[AUTO_START]
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
+51
-30
@@ -22,9 +22,10 @@
|
|||||||
<p class="status"><strong>Implementation status:</strong> Journal format version 1, the immutable Java model,
|
<p class="status"><strong>Implementation status:</strong> Journal format version 1, the immutable Java model,
|
||||||
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
||||||
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
||||||
<code>JavaFxRuntimeService</code>, and the first central component Registry are implemented. Contexts,
|
<code>JavaFxRuntimeService</code>, the first central component Registry, and mandatory Hub configuration
|
||||||
resolution, classloading, discovery, runtime loading/removal, downloading, publishing, synchronization,
|
format 1 are implemented. Contexts, resolution, classloading, discovery, runtime loading/removal,
|
||||||
watching, signatures, permissions, and license filtering remain future work.</p>
|
downloading, publishing, synchronization, watching, signatures, permissions, and license filtering remain
|
||||||
|
future work.</p>
|
||||||
|
|
||||||
<h2>One Journal per artifact</h2>
|
<h2>One Journal per artifact</h2>
|
||||||
<p>A Journal describes exactly one artifact. API, test, and implementation artifacts therefore have independent
|
<p>A Journal describes exactly one artifact. API, test, and implementation artifacts therefore have independent
|
||||||
@@ -117,14 +118,12 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
|||||||
<code>.valuetypes</code>, and <code>.exception</code>, while parser, ingestion, chain state, filesystem
|
<code>.valuetypes</code>, and <code>.exception</code>, while parser, ingestion, chain state, filesystem
|
||||||
loading, and the admin boundary live under <code>.impl.ref</code> without leaking through public
|
loading, and the admin boundary live under <code>.impl.ref</code> without leaking through public
|
||||||
signatures.</p>
|
signatures.</p>
|
||||||
<p>All modern JavaFX applications share one JVM-wide toolkit. The Hub constructs one
|
<p>All modern JavaFX applications share one JVM-wide toolkit. When selected, the Hub constructs one
|
||||||
<code>JavaFxRuntimeServiceImpl</code>, registers it as <code>nenjim.javafx-runtime.service</code>, starts it
|
<code>JavaFxRuntimeServiceImpl</code>, registers it as <code>nenjim.javafx-runtime.service</code>, starts it
|
||||||
through its <code>NenjimProcess</code> view, and injects its lifecycle-free service view into active Mission
|
through its <code>NenjimProcess</code> view, and injects its lifecycle-free service view into later selected
|
||||||
Control and Process Manager. All six modern JavaFX implementations require that service in their constructor.
|
JavaFX applications. Only service startup calls <code>Platform.startup(...)</code>; task submission never
|
||||||
Only service startup calls <code>Platform.startup(...)</code>; task submission never initializes or retries
|
initializes or retries JavaFX and is rejected until startup succeeds. A headless process plan that omits all
|
||||||
JavaFX and is rejected until startup succeeds. Startup waits until the JavaFX Application Thread is ready and
|
JavaFX processes does not construct the runtime or initialize the toolkit.</p>
|
||||||
sets <code>Platform.setImplicitExit(false)</code>, keeping the shared runtime alive after the last window
|
|
||||||
closes.</p>
|
|
||||||
<pre><code>data/nenjim/journals/
|
<pre><code>data/nenjim/journals/
|
||||||
└── <artifact-coordinate>/
|
└── <artifact-coordinate>/
|
||||||
├── <journal-version>.journal
|
├── <journal-version>.journal
|
||||||
@@ -149,33 +148,55 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
|||||||
Registry manager. That manager exposes only registration, is neither a component nor a process, is not
|
Registry manager. That manager exposes only registration, is neither a component nor a process, is not
|
||||||
registered, and cannot start anything. The internal administration interface must remain package-private so
|
registered, and cannot start anything. The internal administration interface must remain package-private so
|
||||||
an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
|
an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
|
||||||
<p><code>NenjimHubImpl</code> is the global composition and runtime owner. It constructs the Registry assembly,
|
<h3>Mandatory NenjimHub.conf format 1</h3>
|
||||||
starts and registers Registry first, and records its successful process ID. It then constructs the Journal
|
<p><code>NenjimHubImpl</code> is the global composition and runtime owner. Before any process starts it reads only
|
||||||
assembly for <code>data/nenjim/journals</code>, registers <code>nenjim.journal.service</code>, starts Journal
|
the readable regular file <code>conf/NenjimHub.conf</code>. The version-controlled
|
||||||
second, and records it only after complete successful loading. Only then does the Hub construct the hardcoded
|
<code>conf/NenjimHub.conf.example</code> is a copy-only template: the application never reads, copies,
|
||||||
business component graph in dependency order. A Journal failure therefore stops startup before Ticker,
|
generates, or falls back to it or to a hardcoded startup sequence.</p>
|
||||||
Evelyn, Burner, the JavaFX runtime, Mission Control, Process Manager, the online point, and the blocking
|
<pre><code>FORMAT_VERSION=1
|
||||||
wait.</p>
|
|
||||||
<p>The Ticker explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains registered but
|
[AUTO_START]
|
||||||
inactive. After Registry and Journal, the Hub activates the explicit Ticker, Production Evelyn, Test Evelyn,
|
nenjim.registry.service
|
||||||
Production IOU Burner, JavaFX runtime service, Mission Control, and Process Manager sequence; it does not
|
nenjim.journal.service</code></pre>
|
||||||
start every registered process. The shared JavaFX runtime is therefore active before either enabled JavaFX
|
<p><code>FORMAT_VERSION=1</code> must be the first actual entry and the one exact
|
||||||
application. Successful process IDs, including <code>nenjim.javafx-runtime.service</code>, are exposed as
|
<code>[AUTO_START]</code> header must be the next actual entry. Leading and trailing whitespace is ignored.
|
||||||
immutable ordered point-in-time snapshots without implying continued health. A JavaFX startup failure is
|
A trimmed line beginning with <code>#</code> is a comment, and <code>#</code> after content begins a trailing
|
||||||
fail-fast and prevents Mission Control and Process Manager from starting.</p>
|
comment. Inside the section a comment-only line does not terminate it, while the first whitespace-only line
|
||||||
|
or end of file does. After termination only blank lines and comments are permitted; there is no quoting or
|
||||||
|
escaping for <code>#</code>.</p>
|
||||||
|
<p>Every active section line is a canonical component ID. The complete list is validated before Registry starts:
|
||||||
|
duplicates, invalid or unknown IDs, passive-only IDs, and IDs the Hub cannot construct as modern processes
|
||||||
|
fail. Registry must occur exactly once first and Journal exactly once second. The known selectable processes
|
||||||
|
are Registry, Journal, Ticker, Jupiter Perps Alarm, both Evelyn services, Production IOU Burner, JavaFX
|
||||||
|
runtime, Mission Control, and Process Manager. The version-controlled template selects only the mandatory
|
||||||
|
Registry and Journal processes; every other process must be added explicitly to the private deployment
|
||||||
|
configuration.</p>
|
||||||
|
<p>After validation the Hub starts, registers, and records Registry, then eagerly constructs and registers the
|
||||||
|
complete passive graph without activating a process. It next visits every remaining configured ID in exact
|
||||||
|
order, beginning with Journal. Each selected process is constructed only when reached, registered before
|
||||||
|
<code>start()</code>, and recorded only after successful return. An omitted process is therefore neither
|
||||||
|
constructed, registered, started, nor displayed by Process Manager.</p>
|
||||||
|
<p><code>[AUTO_START]</code> is the complete manually ordered process dependency closure. The Hub does not infer,
|
||||||
|
insert, recursively construct, or reorder dependencies; JavaFX runtime must precede selected JavaFX
|
||||||
|
applications. Construction, registration, and startup remain fail-fast without rollback. Earlier successful
|
||||||
|
snapshot entries remain truthful after a later failure, and the online point plus blocking wait are reached
|
||||||
|
only after every configured process succeeds.</p>
|
||||||
|
<p>The Ticker still explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains a passive,
|
||||||
|
registered component. Process Manager continues to poll immutable ordered snapshots of actual successful
|
||||||
|
startup milestones rather than displaying configured intent or claiming current health.</p>
|
||||||
<p><code>com.r35157.nenjim.hubd.Main</code> remains only the outer Java bootstrap. It constructs the reference Hub
|
<p><code>com.r35157.nenjim.hubd.Main</code> remains only the outer Java bootstrap. It constructs the reference Hub
|
||||||
through the public <code>NenjimHub</code> interface and calls <code>start()</code> once. The Hub is neither a
|
through the public <code>NenjimHub</code> interface and calls <code>start()</code> once. The Hub is neither a
|
||||||
component nor a process and is not registered inside its own Registry.</p>
|
component nor a process and is not registered inside its own Registry.</p>
|
||||||
<p>The Registry remains internally open to later registration, but this version has one catalogue and no Context,
|
<p>The Registry remains internally open to later registration, but this version has one catalogue and no Context,
|
||||||
version resolution, classloader integration, discovery, public runtime loading or registration, removal,
|
version resolution, classloader integration, discovery, public runtime loading or registration, removal,
|
||||||
persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, or automatic
|
persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, automatic
|
||||||
selection.</p>
|
dependency construction, or automatic start-all behavior.</p>
|
||||||
|
|
||||||
<h2>Future Nenjim vision</h2>
|
<h2>Future Nenjim vision</h2>
|
||||||
<p>Contexts, dependency-graph resolution, automatic selection/reconciliation, runtime upgrade or downgrade,
|
<p>Contexts, dependency-graph resolution, recursive process construction, automatic selection/reconciliation,
|
||||||
classloaders, artifact downloads, IPFS retrieval, Maven dependencies, signatures, publishing, remote
|
runtime upgrade or downgrade, classloaders, artifact downloads, IPFS retrieval, Maven dependencies,
|
||||||
synchronization, filesystem watching, and UI/CLI management are deliberately outside this implemented
|
signatures, publishing, remote synchronization, filesystem watching, and UI/CLI management are deliberately
|
||||||
Journal foundation.</p>
|
outside this implementation.</p>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+81
-27
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
||||||
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
||||||
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService` og første version af det centrale component Registry
|
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService`, første version af det centrale component Registry
|
||||||
> er implementeret. Contexts, resolution, discovery, runtime loading og removal er fortsat fremtidig funktionalitet.
|
> og den obligatoriske format 1 Hub-konfiguration er implementeret. Contexts, resolution, discovery, runtime loading
|
||||||
|
> og removal er fortsat fremtidig funktionalitet.
|
||||||
|
|
||||||
**Indholdsfortegnelse:**
|
**Indholdsfortegnelse:**
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
@@ -21,8 +22,9 @@
|
|||||||
## 1. Introduktion til Nenjim
|
## 1. Introduktion til Nenjim
|
||||||
Nenjim skal på sigt kunne håndtere forskellige versioner af softwareartifakter samtidigt. Det nu implementerede
|
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
|
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. Det første Registry kan desuden katalogisere de nuværende hardcodede
|
en artifakt på et bestemt tidspunkt. Det første Registry kan desuden katalogisere de konstruerede componenter, og
|
||||||
componenter. Dependency-resolution, Contexts, downloading og classloading kommer senere.
|
Hub-konfigurationen vælger den manuelt ordnede procesmængde. Dependency-resolution, Contexts, downloading og
|
||||||
|
classloading kommer senere.
|
||||||
|
|
||||||
## 2. Versionering og Afhængigheder
|
## 2. Versionering og Afhængigheder
|
||||||
Nenjim beskriver moduler, artifakter, releases, relationer og versionspolitik i Journaler. Almindelig Java-kode skal
|
Nenjim beskriver moduler, artifakter, releases, relationer og versionspolitik i Journaler. Almindelig Java-kode skal
|
||||||
@@ -208,11 +210,12 @@ Det kan returnere et immutable registration-order snapshot af ID'er for et compo
|
|||||||
gennem et forventet interface. Et manglende ID giver `null`; et eksisterende ID med forkert interface er en caller
|
gennem et forventet interface. Et manglende ID giver `null`; et eksisterende ID med forkert interface er en caller
|
||||||
programming error. Public API'et kan ikke registrere eller fjerne componenter.
|
programming error. Public API'et kan ikke registrere eller fjerne componenter.
|
||||||
|
|
||||||
`NenjimHubImpl` repræsenterer én kørende Nenjim runtime. Hub'en konstruerer Registry- og Journal-subsystemerne samt
|
`NenjimHubImpl` repræsenterer én kørende Nenjim runtime. Hub'en læser deployment-konfigurationen, ejer dependency
|
||||||
hele den nuværende hardcodede component graph, ejer dependency wiring og vælger de processer, som skal startes. Et
|
wiring og lifecycle, konstruerer hele den nuværende passive component graph og konstruerer kun de processer, som er
|
||||||
implementation-only Registry assembly udleverer read-only service-view, process-view af det samme Registry-objekt
|
valgt til autorun. Et implementation-only Registry assembly udleverer read-only service-view, process-view af det
|
||||||
og en separat `NenjimRegistryServiceManager`. Manageren er kun Hub'ens passive registrationshandle: den er hverken
|
samme Registry-objekt og en separat `NenjimRegistryServiceManager`. Manageren er kun Hub'ens passive
|
||||||
component eller process, har ingen `start()` eller service-accessor, registreres ikke og starter aldrig en component.
|
registrationshandle: den er hverken component eller process, har ingen `start()` eller service-accessor, registreres
|
||||||
|
ikke og starter aldrig en component.
|
||||||
|
|
||||||
Et tilsvarende implementation-only Journal assembly udleverer read-only service-view, process-view af det samme
|
Et tilsvarende implementation-only Journal assembly udleverer read-only service-view, process-view af det samme
|
||||||
Journal-objekt og en separat passiv `JournalServiceManager`. Journal-manageren registreres ikke og giver kun adgang
|
Journal-objekt og en separat passiv `JournalServiceManager`. Journal-manageren registreres ikke og giver kun adgang
|
||||||
@@ -221,29 +224,79 @@ administrationsadgangen.
|
|||||||
|
|
||||||
Alle moderne JavaFX-applikationer deler ét JVM-wide JavaFX toolkit. Det repræsenteres af
|
Alle moderne JavaFX-applikationer deler ét JVM-wide JavaFX toolkit. Det repræsenteres af
|
||||||
`JavaFxRuntimeService`, mens samme `JavaFxRuntimeServiceImpl`-objekt også implementerer `NenjimProcess` for Hub'ens
|
`JavaFxRuntimeService`, mens samme `JavaFxRuntimeServiceImpl`-objekt også implementerer `NenjimProcess` for Hub'ens
|
||||||
lifecycle. Hub'en konstruerer og registrerer objektet som `nenjim.javafx-runtime.service`, starter det før de aktive
|
lifecycle. Når den er valgt, konstruerer og registrerer Hub'en objektet som `nenjim.javafx-runtime.service`; den skal
|
||||||
JavaFX-applikationer og injicerer service-viewet i dem gennem deres constructors. Kun processens `start()` må kalde
|
stå før valgte JavaFX-applikationer i autorun-listen, så dens service-view kan injiceres efter vellykket startup. Kun
|
||||||
`Platform.startup(...)`; `runLater(...)` starter aldrig toolkit'et implicit og afviser arbejde, indtil startup er
|
processens `start()` må kalde `Platform.startup(...)`; `runLater(...)` starter aldrig toolkit'et implicit og afviser
|
||||||
lykkedes. Startup venter på JavaFX Application Thread og sætter `Platform.setImplicitExit(false)`, så den delte
|
arbejde, indtil startup er lykkedes. Startup venter på JavaFX Application Thread og sætter
|
||||||
runtime forbliver aktiv, når det sidste vindue lukkes.
|
`Platform.setImplicitExit(false)`. Udelades alle JavaFX-processer, konstruerer Hub'en ikke runtime-objektet og
|
||||||
|
initialiserer ikke toolkittet.
|
||||||
|
|
||||||
Den package-private `NenjimRegistryServiceAdmin` er managerens interne mutationsadgang. Den må ikke gøres public,
|
Den package-private `NenjimRegistryServiceAdmin` er managerens interne mutationsadgang. Den må ikke gøres public,
|
||||||
fordi almindelige consumers ellers kunne caste deres Registry-service til mutationsinterfacet og bryde det
|
fordi almindelige consumers ellers kunne caste deres Registry-service til mutationsinterfacet og bryde det
|
||||||
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
||||||
registration efter startup.
|
registration efter startup.
|
||||||
|
|
||||||
Applikationen ejer sine konkrete valg og sin activation. Tickerens initiale configuration vælger eksplicit
|
### 8.1 Obligatorisk `NenjimHub.conf` format 1
|
||||||
|
|
||||||
|
Hub'en læser kun `conf/NenjimHub.conf`. Filen skal eksistere som en læsbar regular file; en manglende eller ugyldig
|
||||||
|
fil stopper startup, før Registry startes. `conf/NenjimHub.conf.example` er kun en versioneret copy-template: Hub'en
|
||||||
|
læser, kopierer og genererer den aldrig og falder hverken tilbage til den eller til en hardcoded procesliste.
|
||||||
|
|
||||||
|
Den første faktiske entry skal være `FORMAT_VERSION=1`; før den må der kun stå blanke linjer og full-line
|
||||||
|
`#`-kommentarer. Den næste faktiske entry skal være den ene eksakte `[AUTO_START]`-header. Hver aktiv linje i
|
||||||
|
sektionen er ét `NenjimComponentId`, og rækkefølgen bevares præcist. Leading/trailing whitespace ignoreres, en trimmet
|
||||||
|
linje som begynder med `#` er en kommentar, og `#` efter faktisk indhold starter en trailing kommentar. Inde i
|
||||||
|
sektionen fortsætter en comment-only line listen, mens den første whitespace-only line afslutter sektionen; EOF kan
|
||||||
|
også afslutte den. Efter afslutningen tillades kun blanke linjer og kommentarer. Formatet har ingen quoting eller
|
||||||
|
escaping for `#`.
|
||||||
|
|
||||||
|
Hele listen valideres før første proces-start. Ugyldige, duplikerede, ukendte og passive component-ID'er afvises.
|
||||||
|
Registry skal stå præcis én gang først og Journal præcis én gang som nummer to. De kendte valgbare processer er:
|
||||||
|
|
||||||
|
```text
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
|
assetaz.ticker.default
|
||||||
|
jupiter-perps-alarm.default
|
||||||
|
evelyn.service.prod
|
||||||
|
evelyn.service.test
|
||||||
|
evelyn.iou-burner.prod
|
||||||
|
nenjim.javafx-runtime.service
|
||||||
|
evelyn.mission-control.default
|
||||||
|
nenjim.process-manager.default
|
||||||
|
```
|
||||||
|
|
||||||
|
Efter validering konstruerer og starter Hub'en Registry, registrerer det som `nenjim.registry.service` og registrerer
|
||||||
|
startup-milepælen. Derefter konstrueres og registreres hele den nuværende passive graph uden at starte workers eller
|
||||||
|
processer. Først herefter behandles resten af `[AUTO_START]` i den angivne rækkefølge, begyndende med Journal. Hver
|
||||||
|
valgt proces konstrueres først, når dens linje nås, registreres før `start()` og tilføjes kun til Hub'ens immutable
|
||||||
|
ordered snapshot, når `start()` returnerer. Journal er derfor stadig den anden succesfulde proces, selv om den passive
|
||||||
|
graph konstrueres mellem Registry- og Journal-start.
|
||||||
|
|
||||||
|
`[AUTO_START]` er foreløbig deploymentets komplette, manuelt ordnede proces-dependency closure. Hub'en finder,
|
||||||
|
indsætter eller omordner ikke dependencies. En krævet proces skal stå før sin consumer; eksempelvis skal
|
||||||
|
`nenjim.javafx-runtime.service` stå før Mission Control og Process Manager. Mangler forgængeren, stopper konstruktion
|
||||||
|
eller startup fail-fast uden implicit reparation. En udeladt proces bliver hverken konstrueret, registreret, startet
|
||||||
|
eller vist af Process Manager. Den versionerede template vælger kun de obligatoriske Registry- og Journal-processer;
|
||||||
|
alle øvrige processer skal tilføjes eksplicit til deploymentets private konfiguration.
|
||||||
|
|
||||||
|
Et minimalt headless eksempel, svarende til den versionerede template, er:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
[AUTO_START]
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Tickerens egen component-konfiguration vælger fortsat eksplicit
|
||||||
`assetaz.price-source.raydium-pool.eve-usdt` gennem Registry'et; den hardcodede price source er registreret, men
|
`assetaz.price-source.raydium-pool.eve-usdt` gennem Registry'et; den hardcodede price source er registreret, men
|
||||||
inaktiv. Hub'en starter Registry-processen først og registrerer den som `nenjim.registry.service`. Journal-servicen
|
inaktiv. Fejl under passiv konstruktion, valgt proceskonstruktion, registration eller startup stopper straks og
|
||||||
registreres dernæst som `nenjim.journal.service`, startes som den anden proces og skal være fuldt initialiseret, før
|
forhindrer alle senere processer. Tidligere succeser forbliver synlige, den fejlende proces registreres ikke som
|
||||||
business graph'en fortsætter. Derefter starter Hub'en kun den nuværende eksplicitte, dependency-safe liste: Ticker,
|
startet, og der udføres ingen generic rollback. Online point og det afsluttende blocking wait nås kun efter alle
|
||||||
Production Evelyn, Test Evelyn, Production IOU Burner, JavaFX-runtime-servicen, Mission Control og Process Manager.
|
konfigurerede processer er startet. Process Manager viser fortsat Hub'ens faktiske succesfulde snapshots og ikke den
|
||||||
JavaFX-runtime-servicen startes altså før begge aktive JavaFX-applikationer; Composer, Test Tool, Soda Task Manager
|
ønskede konfigurationsliste.
|
||||||
og Suwimo Client er fortsat inaktive, men bruger samme constructor-injection model. Alarmen bliver heller ikke
|
|
||||||
automatisk aktiveret. Efter hvert succesfuldt `start()` registrerer Hub'en process-ID'et i et immutable, ordered
|
|
||||||
snapshot, inklusive `nenjim.javafx-runtime.service`; et fejlet start bliver ikke registreret, og listen lover ikke
|
|
||||||
fortsat health eller liveness. JavaFX-startfejl stopper opstarten før Mission Control og Process Manager. Startup er
|
|
||||||
fail-fast og kan kun forsøges én gang uden generic rollback.
|
|
||||||
|
|
||||||
`com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap. `Main.main(...)` konstruerer `NenjimHubImpl` gennem
|
`com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap. `Main.main(...)` konstruerer `NenjimHubImpl` gennem
|
||||||
det globale `NenjimHub`-interface og kalder `start()` præcis én gang. Hub'en ejer også det eksisterende online point
|
det globale `NenjimHub`-interface og kalder `start()` præcis én gang. Hub'en ejer også det eksisterende online point
|
||||||
@@ -251,8 +304,9 @@ og den afsluttende blocking wait. Hub'en er hverken component eller process og r
|
|||||||
|
|
||||||
Det implementerede Registry er ét katalog og er ikke et authorization-system. Contexts, artifact-version semantics,
|
Det implementerede Registry er ét katalog og er ikke et authorization-system. Contexts, artifact-version semantics,
|
||||||
resolution, classloading, automatic discovery, public runtime loading/registration, removal, persistence, events,
|
resolution, classloading, automatic discovery, public runtime loading/registration, removal, persistence, events,
|
||||||
automatic selection/start-all, usage tracking og permissions er fremtidigt arbejde. Journal publishing, remote
|
automatic dependency construction/start-all, usage tracking og permissions er fremtidigt arbejde. Den nuværende
|
||||||
synchronization og signature validation er ligeledes ikke implementeret.
|
autorun-fil er ikke Context resolution eller process discovery. Journal publishing, remote synchronization og
|
||||||
|
signature validation er ligeledes ikke implementeret.
|
||||||
|
|
||||||
## 9. Integration med AssetAZ og økonomisk aktivitet
|
## 9. Integration med AssetAZ og økonomisk aktivitet
|
||||||
Økonomi, publicering og distribution er fremtidig vision. Format 1 beskriver alene artifaktviden; det indeholder
|
Økonomi, publicering og distribution er fremtidig vision. Format 1 beskriver alene artifaktviden; det indeholder
|
||||||
|
|||||||
+7
-4
@@ -1,8 +1,8 @@
|
|||||||
# Tekniske termer
|
# Tekniske termer
|
||||||
|
|
||||||
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
||||||
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers og første version af det centrale
|
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers, første version af det centrale
|
||||||
> component Registry er implementeret. Resolver-, Context-,
|
> component Registry og obligatorisk Hub-konfiguration format 1 er implementeret. Resolver-, Context-,
|
||||||
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
||||||
> det er angivet.
|
> det er angivet.
|
||||||
|
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
| Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. `NenjimComponent` er en ren marker uden ID eller lifecycle state. Service, applikation, algoritme og adapter beskriver roller, mens process beskriver en uafhængig lifecycle-klassifikation. |
|
| Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. `NenjimComponent` er en ren marker uden ID eller lifecycle state. Service, applikation, algoritme og adapter beskriver roller, mens process beskriver en uafhængig lifecycle-klassifikation. |
|
||||||
| Applikation | En user-facing eller orchestration-rolle. Rollen gør ikke i sig selv en component til en process; en aktiv applikationsimplementation kan separat implementere `NenjimProcess`. |
|
| Applikation | En user-facing eller orchestration-rolle. Rollen gør ikke i sig selv en component til en process; en aktiv applikationsimplementation kan separat implementere `NenjimProcess`. |
|
||||||
| NenjimProcess | Det canonical `com.r35157.nenjim.process.NenjimProcess` component interface for noget, som Hub'en kan starte og tracke. Det eksponerer kun `start()` og lover ikke stop, restart, health, Context eller dependency lookup. |
|
| NenjimProcess | Det canonical `com.r35157.nenjim.process.NenjimProcess` component interface for noget, som Hub'en kan starte og tracke. Det eksponerer kun `start()` og lover ikke stop, restart, health, Context eller dependency lookup. |
|
||||||
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en konstruerer og registrerer component graph'en, ejer dependency wiring og startup-policy, tracker succesfuldt startede process-ID'er, starter den fælles JavaFX-runtime før dens applikationer, erklærer online point og ejer den afsluttende runtime wait. Den er hverken component eller process og registreres ikke i sit eget Registry. |
|
| Passiv component | En `NenjimComponent`, hvis implementation ikke deltager i Hub-lifecycle gennem det canonical `NenjimProcess`. Den nuværende Hub konstruerer og registrerer hele den passive dependency graph efter Registry-start uafhængigt af `[AUTO_START]`, men starter den ikke. |
|
||||||
|
| Valgt proces | Et kendt `NenjimProcess`-objekt, som Hub'en kun konstruerer, registrerer og starter, når dets ID forekommer i `[AUTO_START]`. Rollen som service eller applikation afgør ikke denne klassifikation. |
|
||||||
|
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en validerer `conf/NenjimHub.conf` før proces-start, bootstrapper Registry, konstruerer den passive graph og konstruerer/registrerer/starter kun valgte processer i eksakt konfigurationsrækkefølge. Den tracker succesfulde start-ID'er, erklærer online point og ejer den afsluttende runtime wait, men er hverken component eller process og registreres ikke i sit eget Registry. |
|
||||||
|
| AUTO_START | Den obligatoriske, ordered sektion i `NenjimHub.conf` format 1. Den er deploymentets komplette manuelle proces-dependency closure; Registry og Journal skal være første og anden entry. Den udtrykker ikke Context resolution, discovery eller automatisk dependency construction. |
|
||||||
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
||||||
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
||||||
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
||||||
@@ -41,7 +44,7 @@
|
|||||||
| JournalService | Registrerbart, filesystem-uafhængigt og read-only Service-API for current og historical Journal-queries. Det eksponerer ingen lifecycle-, path-, refresh-, parser- eller mutationsoperationer, og queries er gated indtil vellykket proces-startup. |
|
| JournalService | Registrerbart, filesystem-uafhængigt og read-only Service-API for current og historical Journal-queries. Det eksponerer ingen lifecycle-, path-, refresh-, parser- eller mutationsoperationer, og queries er gated indtil vellykket proces-startup. |
|
||||||
| JournalServiceManager | Passivt, uregistreret administrationshandle for én Journal-service med kun normalized data root og explicit refresh. Hub'en beholder det; det har ingen start, stop, service-accessor eller query-operationer. |
|
| JournalServiceManager | Passivt, uregistreret administrationshandle for én Journal-service med kun normalized data root og explicit refresh. Hub'en beholder det; det har ingen start, stop, service-accessor eller query-operationer. |
|
||||||
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
||||||
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Hub'en ejer dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. |
|
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Når processen er valgt, ejer Hub'en dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. En headless konfiguration kan udelade runtime og alle JavaFX-applikationer helt. |
|
||||||
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
||||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||||
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-09-03
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation and the delta specifications for observable behavior. `NenjimHubImpl` currently mixes passive and active construction in shared initialization methods, then resolves and starts a fixed list of already-registered processes. Registry is the bootstrap exception, Journal currently starts before the rest of composition, and process dependencies are retained as concrete Hub fields.
|
||||||
|
|
||||||
|
The change must keep the public Hub, Registry, process, Journal, and JavaFX APIs unchanged. Human-written Java remains under `src/main/tjava`; Detag remains part of the normal Gradle build. Issue #81 excludes general automated tests, so verification uses strict OpenSpec validation, source inspection, and the complete Gradle build without running the production Hub.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Make configuration parsing and all statically knowable validation complete before Registry startup.
|
||||||
|
- Make the passive/process boundary visible in composition structure and ensure no process constructor runs in the passive phase.
|
||||||
|
- Represent process construction capability without preconstructing instances or encoding a second startup order.
|
||||||
|
- Preserve the existing component IDs, concrete implementations, injected dependencies, single-attempt lifecycle, startup snapshots, online point, and wait behavior.
|
||||||
|
- Let missing predecessor processes fail through the existing constructor/start contracts without adding implicit dependency behavior.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- No reusable public configuration framework, process factory, graph resolver, dependency metadata, test hook, or alternative Hub constructor.
|
||||||
|
- No refactoring of embedded operational configuration unrelated to process selection.
|
||||||
|
- No cleanup of legacy Hub/process prototypes and no change to component-specific lifecycle behavior.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Parse into one immutable internal startup plan
|
||||||
|
|
||||||
|
Add a package-private Hub configuration value and parser under `com.r35157.nenjim.hubd.impl.ref`. The parser reads `conf/NenjimHub.conf` as UTF-8, checks file kind/readability, and returns an immutable ordered list of canonical `NenjimComponentId` values. It receives the implementation's known selectable ID set so syntax, duplicates, capability, and mandatory placement are all rejected in one pre-start phase.
|
||||||
|
|
||||||
|
The parser uses an explicit state machine for before-version, before-section, inside-section, and after-section states. It distinguishes a whitespace-only source line from a comment-only line before removing trailing `#` content, which preserves the issue's unusual rule that blank lines terminate `[AUTO_START]` while comments do not. Error helpers attach path and one-based line details consistently.
|
||||||
|
|
||||||
|
Using the existing general format-version validator was considered. It does not by itself model section termination and would require a parallel preprocessing/state layer; keeping the complete grammar in the Hub parser avoids two competing interpretations and permits more precise diagnostics.
|
||||||
|
|
||||||
|
### Use a constructor catalogue, not an autorun list
|
||||||
|
|
||||||
|
`NenjimHubImpl` owns an implementation-only map from every non-Registry selectable process ID to a no-argument constructor operation. Bound method references describe capability but do not create process instances. Registry remains an explicit bootstrap exception and is added only to the known-ID validation set. Map iteration order is never used; the validated configuration list is the sole startup order.
|
||||||
|
|
||||||
|
A switch statement was considered but would scatter selection, construction, and validation membership. A public or Registry-provided factory was rejected because discovery and extension APIs are outside scope.
|
||||||
|
|
||||||
|
### Split eager passive composition from lazy process construction
|
||||||
|
|
||||||
|
Existing building blocks, price sources, Jupiter services, the swap service, and notification service remain eagerly constructed and registered after Registry startup. Ticker, both Evelyn services, the Burner, Jupiter Perps Alarm, JavaFX runtime, Mission Control, Process Manager, and Journal move to individual lazy constructor operations because their concrete implementations participate in `NenjimProcess`.
|
||||||
|
|
||||||
|
Each lazy operation assigns the same Hub field used by downstream constructors, but returns the newly constructed canonical process before registration. This preserves current dependency wiring: for example, Evelyn construction fails if Ticker has not already been selected, and Mission Control fails if its configured Evelyn or JavaFX predecessors are absent. No null placeholder is registered and no constructor is invoked speculatively.
|
||||||
|
|
||||||
|
Journal's lazy operation also retains its passive manager from the assembly. The Registry assembly remains special because Registry must start before any ordinary registration can be queried.
|
||||||
|
|
||||||
|
### Apply one registration-before-start path to configured processes
|
||||||
|
|
||||||
|
For each configured ID after Registry, the Hub looks up its constructor operation, constructs the process, registers it under the configured ID, resolves the registered object through `NenjimProcess`, starts it, and records the ID only after successful return. A constructor failure therefore precedes registration; a start failure can leave the registered instance visible but cannot enter the successful-start snapshot. Exceptions propagate immediately, so later map entries are irrelevant and later configured IDs are untouched.
|
||||||
|
|
||||||
|
Registry retains its required exceptional order of start, self-registration, then recording. The online announcement and blocking wait remain after the configuration loop only.
|
||||||
|
|
||||||
|
### Keep deployment configuration separate from its template
|
||||||
|
|
||||||
|
The runtime path stays a private constant in the reference Hub. `conf/NenjimHub.conf.example` is version controlled and documented, but no code references that example path. The example deliberately contains only the mandatory Registry and Journal entries so it is safe and generic for downstream users; operators add their own deployment-specific processes and dependency order after copying it. The real file remains deployment-specific under the existing `*.conf` ignore rule. Documentation shows copying explicitly and includes the same minimal headless bootstrap.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Constructor-order coupling remains implicit in fields] → Document that `[AUTO_START]` is the complete manual dependency closure and keep failures clear; dependency metadata is intentionally deferred.
|
||||||
|
- [A selected process constructor may assign a Hub field before later registration fails] → Do not retry or roll back; fail-fast startup is single-attempt and the Registry remains the observable boundary.
|
||||||
|
- [Parser edge cases around comments and blank lines are easy to conflate] → Decide termination from the original trimmed line before trailing-comment removal and keep parser states explicit.
|
||||||
|
- [A future process may be added to construction but forgotten in validation] → Derive known non-Registry IDs from the same constructor catalogue used at runtime and add Registry programmatically.
|
||||||
|
- [Headless verification could accidentally initialize JavaFX] → Do not run the production Hub during verification; inspect constructor placement and rely on the complete compilation/build path.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Deploy the new build together with an operator-created `conf/NenjimHub.conf` copied from the version 1 example and extended with the deployment's own ordered process selection.
|
||||||
|
2. Validate that manually ordered dependencies precede their consumers; Registry and Journal must remain first and second.
|
||||||
|
3. Start the Hub normally through the unchanged `Main` entry point.
|
||||||
|
4. To roll back the software, restore the previous build and its prior deployment configuration expectations; the application never rewrites either runtime or example configuration.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
`NenjimHubImpl` currently constructs the complete active process graph and starts a fixed process sequence, so one build cannot serve deployment roles such as a headless server without also constructing JavaFX processes. Issue #81 introduces an explicit, validated deployment configuration now, while process dependency discovery and recursive construction remain future work.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING**: Require the Hub to read `conf/NenjimHub.conf` in strict format version 1 before any process starts; missing, unreadable, non-regular, malformed, or unsupported configuration fails startup without fallback.
|
||||||
|
- Add one mandatory ordered `[AUTO_START]` section whose active lines are canonical `NenjimComponentId` values, with defined whitespace, `#` comment, and blank-line termination rules.
|
||||||
|
- Validate the complete requested process sequence before Registry startup, including known process capability, uniqueness, and Registry/Journal placement as the first two entries.
|
||||||
|
- Bootstrap Registry, then eagerly construct and register the complete passive component graph, while lazily constructing, registering, and starting only the remaining configured process objects in exact configuration order.
|
||||||
|
- Preserve registration-before-start, post-success startup recording, fail-fast partial-startup semantics, the online point, and the blocking runtime wait without rollback or dependency inference.
|
||||||
|
- Make all modern Hub-known processes selectable, including the otherwise inactive Jupiter Perps Alarm, while an omitted process is neither constructed, registered, nor started.
|
||||||
|
- Add a documented, copy-only `conf/NenjimHub.conf.example` that provides a generic minimal bootstrap with only the mandatory Registry and Journal processes, plus headless configuration documentation.
|
||||||
|
- Remove the unconditional process-construction path and fixed startup list without adding public factory, discovery, lifecycle, Context, or configuration APIs.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `nenjim-hub-configuration`: Defines the mandatory format 1 Hub configuration grammar, validation, ordered bootstrap and startup plan, lazy selected-process construction, omitted-process behavior, template role, and failure semantics.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `nenjim-component-registry`: Replaces the Hub's complete hardcoded process graph and fixed startup subset with eager passive registration plus configuration-selected process construction and registration.
|
||||||
|
- `javafx-runtime-service`: Replaces the fixed Hub ordering guarantee with explicit dependency-safe configuration ordering and permits a valid headless Hub configuration to omit every JavaFX process.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Adds an implementation-only Hub configuration parser/model and process-construction catalogue under `com.r35157.nenjim.hubd.impl.ref` without changing the public `NenjimHub` API.
|
||||||
|
- Refactors `NenjimHubImpl` composition so Registry remains the bootstrap exception, passive dependencies remain eager, and process instances become lazy.
|
||||||
|
- Adds `conf/NenjimHub.conf.example` while retaining the ignored deployment-specific runtime file at `conf/NenjimHub.conf`.
|
||||||
|
- Updates Nenjim architecture and terminology documentation to describe format 1, manual dependency ordering, a generic minimal template, headless operation, and actual successful-start snapshots.
|
||||||
|
- Adds no automated tests, external dependencies, process rollback, automatic dependency resolution, dynamic discovery, or command-line configuration selection.
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Deferred JavaFX runtime features remain absent
|
||||||
|
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. Hub configuration MAY select the JavaFX runtime and applications only through explicit known process IDs in dependency-safe order; it SHALL not add configuration to the JavaFX service API. A valid headless Hub configuration MAY omit the runtime and every JavaFX application, in which case the Hub SHALL not construct or initialize the JavaFX runtime.
|
||||||
|
|
||||||
|
#### Scenario: Configured JavaFX consumers follow runtime startup
|
||||||
|
- **WHEN** a valid Hub configuration selects JavaFX applications
|
||||||
|
- **THEN** it lists `nenjim.javafx-runtime.service` before those applications and the shared runtime is started before they submit UI work
|
||||||
|
|
||||||
|
#### Scenario: Headless configuration omits JavaFX
|
||||||
|
- **WHEN** a valid Hub configuration omits the JavaFX runtime and all JavaFX applications
|
||||||
|
- **THEN** no JavaFX runtime or application process is constructed and the JavaFX toolkit remains uninitialized by Hub startup
|
||||||
|
|
||||||
|
#### Scenario: Runtime boundary is inspected
|
||||||
|
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
||||||
|
- **THEN** only explicit one-time runtime startup, asynchronous task submission, configuration-ordered dependency injection, and optional omission are present
|
||||||
|
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Main is a thin outer bootstrap
|
||||||
|
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not select a configuration path or construct the Registry, manager, or business graph.
|
||||||
|
|
||||||
|
#### Scenario: Start through the outer bootstrap
|
||||||
|
- **WHEN** the configured Java entry point is invoked
|
||||||
|
- **THEN** `Main.main(...)` constructs one reference Hub through the public Hub interface and delegates startup to it exactly once
|
||||||
|
|
||||||
|
#### Scenario: No duplicate composition path remains
|
||||||
|
- **WHEN** the source tree is inspected after migration
|
||||||
|
- **THEN** the former Registry-manager and hardcoded autorun paths are absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of configuration-directed composition
|
||||||
|
|
||||||
|
#### Scenario: Bootstrap has no composition responsibility
|
||||||
|
- **WHEN** the outer bootstrap source is inspected
|
||||||
|
- **THEN** configuration parsing, composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
||||||
|
|
||||||
|
### Requirement: Deferred runtime features remain absent
|
||||||
|
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. Hub autorun configuration SHALL remain a private startup input that selects only the implementation's known process constructors in explicit manual order. Except for that selection and the Process Manager's read-only polling of the existing started-process snapshot, the Hub SHALL not add lifecycle events, subscriptions, process controls, stop, restart, upgrade, health monitoring, recovery, automatic dependency construction, or context-bound dependency access.
|
||||||
|
|
||||||
|
#### Scenario: Consumer inspects the first Registry API
|
||||||
|
- **WHEN** a consumer examines the public Registry, Registry-manager, process, Hub, and Process Manager contracts
|
||||||
|
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, Hub startup/snapshots, and the Process Manager role are available and configuration, discovery, resolution, and control remain absent from public APIs
|
||||||
|
|
||||||
|
### Requirement: NenjimHub owns composition and runtime startup
|
||||||
|
`NenjimHub` SHALL remain the global runtime owner exposing only `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither component nor process and SHALL not register itself. Its only accepted startup attempt SHALL validate the complete fixed-path Hub configuration before starting anything, construct and start Registry first, register Registry as `nenjim.registry.service`, record it, construct and register the complete passive graph, then lazily construct, register, start, and record only the remaining configured processes in exact order beginning with Journal. Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub. Registration SHALL never imply startup. The Hub SHALL preserve the online point and blocking wait only after all configured processes succeed, without retry, rollback, dependency inference, or reordering.
|
||||||
|
|
||||||
|
#### Scenario: Registry starts before passive graph construction
|
||||||
|
- **WHEN** the Hub has validated a complete startup plan and performs its first startup attempt
|
||||||
|
- **THEN** it starts, registers, and records Registry first before constructing the passive component graph or any selected non-Registry process
|
||||||
|
|
||||||
|
#### Scenario: Journal follows passive composition but starts second
|
||||||
|
- **WHEN** Registry bootstrap and passive graph construction have succeeded
|
||||||
|
- **THEN** the Hub constructs, registers, starts, and records Journal before any later configured process, so Journal remains the second successful process
|
||||||
|
|
||||||
|
#### Scenario: Registry is process-discoverable
|
||||||
|
- **WHEN** the Registry registration has completed
|
||||||
|
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||||
|
|
||||||
|
#### Scenario: Journal is process-discoverable
|
||||||
|
- **WHEN** the configured Journal construction and registration have completed
|
||||||
|
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||||
|
|
||||||
|
#### Scenario: Selected JavaFX runtime is process-discoverable
|
||||||
|
- **WHEN** `nenjim.javafx-runtime.service` is selected and its registration has completed
|
||||||
|
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under that ID
|
||||||
|
|
||||||
|
#### Scenario: Selected JavaFX applications share the runtime
|
||||||
|
- **WHEN** JavaFX runtime precedes selected Mission Control or Process Manager entries
|
||||||
|
- **THEN** each selected application retains the same already-constructed `JavaFxRuntimeService` instance registered by the Hub
|
||||||
|
|
||||||
|
#### Scenario: Selected Process Manager is process-discoverable
|
||||||
|
- **WHEN** `nenjim.process-manager.default` is selected after JavaFX runtime and its construction and registration complete
|
||||||
|
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess`
|
||||||
|
|
||||||
|
#### Scenario: Required passive bindings are registered
|
||||||
|
- **WHEN** Hub passive composition completes
|
||||||
|
- **THEN** all current passive component IDs and implementation choices from issue #76 are present exactly as configured, while process IDs other than Registry remain absent until selected
|
||||||
|
|
||||||
|
#### Scenario: Configured startup subset preserves order
|
||||||
|
- **WHEN** Hub startup completes successfully
|
||||||
|
- **THEN** successful process IDs exactly equal the validated `[AUTO_START]` entries in their declared order
|
||||||
|
|
||||||
|
#### Scenario: Omitted processes do not enter the Registry
|
||||||
|
- **WHEN** a modern process ID is omitted from `[AUTO_START]`
|
||||||
|
- **THEN** that process is neither constructed, registered, started, nor recorded even though its implementation is known to the Hub
|
||||||
|
|
||||||
|
#### Scenario: Journal failure is fail-fast
|
||||||
|
- **WHEN** Journal construction, registration, or process startup throws
|
||||||
|
- **THEN** Registry remains the only recorded process, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
|
#### Scenario: JavaFX runtime failure is fail-fast
|
||||||
|
- **WHEN** selected JavaFX runtime construction, registration, or startup throws
|
||||||
|
- **THEN** its ID is not recorded, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
|
#### Scenario: Hub start is not retried
|
||||||
|
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||||
|
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||||
|
|
||||||
|
#### Scenario: Startup failure is fail-fast
|
||||||
|
- **WHEN** any passive composition, selected construction, registration, or process startup operation throws
|
||||||
|
- **THEN** the exception propagates, no later process is constructed or started, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||||
|
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines the mandatory deployment configuration that selects which modern Nenjim processes the Hub constructs and starts, while preserving explicit order and fail-fast lifecycle semantics.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Hub startup requires one fixed runtime configuration
|
||||||
|
The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all configuration parsing and validation before starting Registry or any other process. The path SHALL not be configurable through `Main`, command-line arguments, or the public `NenjimHub` API. A missing, unreadable, or non-regular runtime file SHALL fail Hub startup clearly and identify the configured path. The Hub SHALL not read, copy, generate, or fall back to `conf/NenjimHub.conf.example`, and SHALL not fall back to a hardcoded startup list.
|
||||||
|
|
||||||
|
#### Scenario: Runtime configuration is valid
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is a readable regular file containing a valid format 1 configuration
|
||||||
|
- **THEN** the Hub derives its complete requested process order from that file before starting Registry
|
||||||
|
|
||||||
|
#### Scenario: Runtime configuration cannot be read
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is missing, unreadable, or not a regular file
|
||||||
|
- **THEN** startup fails with a diagnostic identifying `conf/NenjimHub.conf` before any process starts
|
||||||
|
|
||||||
|
#### Scenario: Only the example exists
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is absent but `conf/NenjimHub.conf.example` exists
|
||||||
|
- **THEN** startup fails without reading, copying, or using the example and without starting a hardcoded fallback set
|
||||||
|
|
||||||
|
### Requirement: Format 1 has one exact ordered AUTO_START section
|
||||||
|
The first actual configuration entry SHALL be exactly `FORMAT_VERSION=1`, after optional leading blank lines and full-line comments. Version 1 SHALL be the only supported version; a missing, malformed, duplicate, version 2, or otherwise unsupported declaration SHALL fail. The next actual entry SHALL be exactly one `[AUTO_START]` header, optionally followed only by a trailing `#` comment. Unknown, malformed, repeated, valued, or out-of-order sections and entries before `[AUTO_START]` SHALL fail.
|
||||||
|
|
||||||
|
Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line whose trimmed content begins with `#` SHALL be a full-line comment, and `#` after actual content SHALL begin a trailing comment. Inside `[AUTO_START]`, surrounding whitespace around a process ID SHALL be ignored; full-line and trailing comments SHALL be ignored; comment-only lines SHALL not terminate the section; and the first whitespace-only line or end of file SHALL terminate it. After termination, only blank lines and comments SHALL be accepted. Format 1 SHALL provide no quoting or escaping for `#`.
|
||||||
|
|
||||||
|
#### Scenario: Leading comments and trailing comments are accepted
|
||||||
|
- **WHEN** blank lines or full-line comments precede `FORMAT_VERSION=1`, and the version, section header, or process entries contain trailing `#` comments
|
||||||
|
- **THEN** comment text and permitted surrounding whitespace are ignored while the actual entries are parsed normally
|
||||||
|
|
||||||
|
#### Scenario: Comment inside AUTO_START does not terminate it
|
||||||
|
- **WHEN** a comment-only line appears between two process IDs in `[AUTO_START]`
|
||||||
|
- **THEN** both process IDs remain part of the ordered section
|
||||||
|
|
||||||
|
#### Scenario: Empty line terminates AUTO_START
|
||||||
|
- **WHEN** a whitespace-only line appears after at least one `[AUTO_START]` entry
|
||||||
|
- **THEN** the section terminates at that line and later actual content fails as unexpected content
|
||||||
|
|
||||||
|
#### Scenario: End of file terminates AUTO_START
|
||||||
|
- **WHEN** the final process ID is immediately followed by end of file
|
||||||
|
- **THEN** the section terminates successfully at end of file
|
||||||
|
|
||||||
|
#### Scenario: Version declaration is invalid
|
||||||
|
- **WHEN** the version is absent, malformed, duplicated, not the first actual entry, or has a value other than `1`
|
||||||
|
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line and reason
|
||||||
|
|
||||||
|
#### Scenario: Section structure is invalid
|
||||||
|
- **WHEN** `[AUTO_START]` is absent, duplicated, misspelled, has a value or non-comment trailing token, follows another actual entry, or is replaced by an unknown section
|
||||||
|
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line, offending content and reason
|
||||||
|
|
||||||
|
### Requirement: The complete requested process plan is validated before startup
|
||||||
|
Every active `[AUTO_START]` line SHALL be parsed through the canonical `NenjimComponentId` rules and retained in source order with its line location. Before Registry starts, validation SHALL reject invalid ID syntax, duplicate IDs, unknown IDs, passive-only component IDs, and IDs the reference Hub cannot construct as canonical `NenjimProcess` objects. The section SHALL contain Registry exactly once at position one and Journal exactly once at position two. Applicable failures SHALL identify `conf/NenjimHub.conf`, the relevant line, offending value, and reason.
|
||||||
|
|
||||||
|
#### Scenario: Process ID is malformed
|
||||||
|
- **WHEN** an active section line is not a valid `NenjimComponentId`
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the path, line, offending value and component-ID reason
|
||||||
|
|
||||||
|
#### Scenario: Process ID is duplicated
|
||||||
|
- **WHEN** the same canonical process ID appears more than once
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the duplicate value and line
|
||||||
|
|
||||||
|
#### Scenario: ID is not constructible as a process
|
||||||
|
- **WHEN** an entry is unknown, identifies only a passive component, or is not in the Hub's modern process-construction catalogue
|
||||||
|
- **THEN** validation fails before Registry startup rather than constructing or starting any process
|
||||||
|
|
||||||
|
#### Scenario: Bootstrap entries are not first and second
|
||||||
|
- **WHEN** Registry is absent or not first, or Journal is absent or not second
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the required placement
|
||||||
|
|
||||||
|
### Requirement: Registry bootstraps eager passive composition
|
||||||
|
After validating the entire plan, the Hub SHALL construct the configured Registry subsystem, start Registry, register its service and process object as `nenjim.registry.service`, and record that ID only after successful startup. The Hub SHALL then eagerly construct and register the complete current passive component graph needed by selectable processes. This passive phase SHALL not construct any canonical `NenjimProcess`, start background workers, initialize JavaFX, add startup IDs, infer process dependencies, or implicitly create an omitted process. Plain internal dependencies that are not `NenjimComponent` objects MAY be constructed as part of passive composition.
|
||||||
|
|
||||||
|
#### Scenario: Registry surrounds passive composition
|
||||||
|
- **WHEN** a valid Hub startup begins
|
||||||
|
- **THEN** Registry starts, is registered, and is recorded before the complete passive component graph is constructed and registered
|
||||||
|
|
||||||
|
#### Scenario: Passive composition completes
|
||||||
|
- **WHEN** Registry bootstrap succeeds
|
||||||
|
- **THEN** all current non-process building blocks, adapters, and services are constructed and registered without starting a process
|
||||||
|
|
||||||
|
#### Scenario: Passive construction fails
|
||||||
|
- **WHEN** any passive component cannot be constructed or registered
|
||||||
|
- **THEN** startup stops immediately with Registry remaining recorded and no configured process after Registry constructed or started
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any process-specific passive administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
### Requirement: Configured startup is fail-fast and reports actual milestones
|
||||||
|
Registry and every selected process SHALL be registered before its `start()` call and appended to the Hub's immutable ordered startup snapshot only after `start()` returns successfully. Construction failure SHALL leave the selected process unregistered. Registration or startup failure SHALL stop the plan before any later entry is constructed or started; a startup failure MAY leave that process registered but SHALL not record it. Earlier successful processes SHALL remain in snapshots, no rollback or retry SHALL occur, and the Hub SHALL announce its existing online point and enter its blocking runtime wait only after every configured process succeeds.
|
||||||
|
|
||||||
|
#### Scenario: Selected construction fails
|
||||||
|
- **WHEN** construction of the current selected process throws
|
||||||
|
- **THEN** the process is not registered or recorded, no later process is constructed, and the Hub does not announce online or enter its final wait
|
||||||
|
|
||||||
|
#### Scenario: Selected registration fails
|
||||||
|
- **WHEN** registration of the constructed current process throws
|
||||||
|
- **THEN** the process is not started or recorded, no later process is constructed, and no rollback is attempted
|
||||||
|
|
||||||
|
#### Scenario: Selected startup fails
|
||||||
|
- **WHEN** the current process's `start()` throws
|
||||||
|
- **THEN** it remains unrecorded, no later process is constructed or started, earlier successful IDs remain visible, and no rollback is attempted
|
||||||
|
|
||||||
|
#### Scenario: Configured startup completes
|
||||||
|
- **WHEN** all configured process starts return successfully
|
||||||
|
- **THEN** the startup snapshot has the exact configured order, the online point is announced, and the Hub enters its existing blocking wait
|
||||||
|
|
||||||
|
### Requirement: The version-controlled example is a copy-only template
|
||||||
|
The repository SHALL contain `conf/NenjimHub.conf.example` as a documented format 1 copy-only template with only the mandatory Registry and Journal entries active. It SHALL contain no optional deployment-specific process IDs. Its comments SHALL state that the file must be copied to `conf/NenjimHub.conf` before use, Registry and Journal are mandatory, order is preserved, and process dependencies must be listed manually before their consumers. Documentation SHALL show the same minimal bootstrap as a headless example and SHALL describe recursive dependency construction as future work rather than implemented behavior.
|
||||||
|
|
||||||
|
#### Scenario: Example provides a generic minimal bootstrap
|
||||||
|
- **WHEN** the example's uncommented process IDs are read in order
|
||||||
|
- **THEN** they are only Registry and Journal, with no optional deployment-specific process ID present
|
||||||
|
|
||||||
|
#### Scenario: Operator uses the minimal headless example
|
||||||
|
- **WHEN** the documented headless configuration is copied to the runtime path
|
||||||
|
- **THEN** it selects only Registry and Journal and leaves every optional process for the operator to add explicitly
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
## 1. Hub configuration
|
||||||
|
|
||||||
|
- [x] 1.1 Add the package-private immutable Hub configuration model and strict format 1 parser for the fixed runtime path, including exact version/section/comment/termination grammar and path-and-line diagnostics.
|
||||||
|
- [x] 1.2 Validate canonical process IDs, duplicates, known process construction capability, and mandatory Registry/Journal positions before Registry startup.
|
||||||
|
|
||||||
|
## 2. Ordered runtime composition
|
||||||
|
|
||||||
|
- [x] 2.1 Split `NenjimHubImpl` composition so the complete passive dependency graph is constructed and registered only after successful Registry bootstrap and contains no canonical process instance.
|
||||||
|
- [x] 2.2 Add an implementation-only lazy constructor catalogue for every supported modern non-Registry process, including optional Jupiter Perps Alarm, without encoding startup order.
|
||||||
|
- [x] 2.3 Drive construction, registration, canonical process resolution, startup, and post-success recording from the validated configuration order while preserving fail-fast partial-startup behavior, online announcement, and final wait.
|
||||||
|
|
||||||
|
## 3. Configuration template and documentation
|
||||||
|
|
||||||
|
- [x] 3.1 Add `conf/NenjimHub.conf.example` as a documented copy-only format 1 template containing only the mandatory Registry and Journal bootstrap entries, without deployment-specific optional processes.
|
||||||
|
- [x] 3.2 Update Nenjim architecture, public documentation, and terminology for mandatory Hub configuration, exact grammar, manual dependency order, passive versus selected construction, omission, failure semantics, and headless operation.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Inspect the implementation to confirm no runtime reference to the example file, no fixed autorun sequence, no eager process construction, and no public API expansion remains.
|
||||||
|
- [x] 4.2 Run strict OpenSpec validation and the complete Gradle build, resolving all failures without running the production Hub or adding general automated tests.
|
||||||
@@ -72,9 +72,16 @@ The implementation's `start()` SHALL be the only operation that calls `Platform.
|
|||||||
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
||||||
|
|
||||||
### Requirement: Deferred JavaFX runtime features remain absent
|
### Requirement: Deferred JavaFX runtime features remain absent
|
||||||
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. This change SHALL not add `NenjimHub.conf` or configurable autorun.
|
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. Hub configuration MAY select the JavaFX runtime and applications only through explicit known process IDs in dependency-safe order; it SHALL not add configuration to the JavaFX service API. A valid headless Hub configuration MAY omit the runtime and every JavaFX application, in which case the Hub SHALL not construct or initialize the JavaFX runtime.
|
||||||
|
|
||||||
|
#### Scenario: Configured JavaFX consumers follow runtime startup
|
||||||
|
- **WHEN** a valid Hub configuration selects JavaFX applications
|
||||||
|
- **THEN** it lists `nenjim.javafx-runtime.service` before those applications and the shared runtime is started before they submit UI work
|
||||||
|
|
||||||
|
#### Scenario: Headless configuration omits JavaFX
|
||||||
|
- **WHEN** a valid Hub configuration omits the JavaFX runtime and all JavaFX applications
|
||||||
|
- **THEN** no JavaFX runtime or application process is constructed and the JavaFX toolkit remains uninitialized by Hub startup
|
||||||
|
|
||||||
#### Scenario: Runtime boundary is inspected
|
#### Scenario: Runtime boundary is inspected
|
||||||
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
||||||
- **THEN** only explicit one-time startup, asynchronous task submission, fixed Hub ordering, and dependency injection are present
|
- **THEN** only explicit one-time runtime startup, asynchronous task submission, configuration-ordered dependency injection, and optional omission are present
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ A non-bootstrap application or service that owns a component choice SHALL receiv
|
|||||||
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
||||||
|
|
||||||
### Requirement: Main is a thin outer bootstrap
|
### Requirement: Main is a thin outer bootstrap
|
||||||
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not construct the Registry, manager, or business graph.
|
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not select a configuration path or construct the Registry, manager, or business graph.
|
||||||
|
|
||||||
#### Scenario: Start through the outer bootstrap
|
#### Scenario: Start through the outer bootstrap
|
||||||
- **WHEN** the configured Java entry point is invoked
|
- **WHEN** the configured Java entry point is invoked
|
||||||
@@ -116,11 +116,11 @@ A non-bootstrap application or service that owns a component choice SHALL receiv
|
|||||||
|
|
||||||
#### Scenario: No duplicate composition path remains
|
#### Scenario: No duplicate composition path remains
|
||||||
- **WHEN** the source tree is inspected after migration
|
- **WHEN** the source tree is inspected after migration
|
||||||
- **THEN** the former Registry-manager composition path is absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of the current hardcoded component graph
|
- **THEN** the former Registry-manager and hardcoded autorun paths are absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of configuration-directed composition
|
||||||
|
|
||||||
#### Scenario: Bootstrap has no composition responsibility
|
#### Scenario: Bootstrap has no composition responsibility
|
||||||
- **WHEN** the outer bootstrap source is inspected
|
- **WHEN** the outer bootstrap source is inspected
|
||||||
- **THEN** composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
- **THEN** configuration parsing, composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
||||||
|
|
||||||
### Requirement: Registry terminology and package boundaries are consistent
|
### Requirement: Registry terminology and package boundaries are consistent
|
||||||
Public component contracts SHALL live in `com.r35157.nenjim.component`; the canonical process contract SHALL live in `com.r35157.nenjim.process`; the global Hub contract SHALL live in `com.r35157.nenjim.hubd`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; and the component ID SHALL live in its `.valuetypes` package. Registry storage and its package-private administration view SHALL live in `.impl.ref`, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use `.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
Public component contracts SHALL live in `com.r35157.nenjim.component`; the canonical process contract SHALL live in `com.r35157.nenjim.process`; the global Hub contract SHALL live in `com.r35157.nenjim.hubd`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; and the component ID SHALL live in its `.valuetypes` package. Registry storage and its package-private administration view SHALL live in `.impl.ref`, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use `.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
||||||
@@ -138,11 +138,11 @@ Public component contracts SHALL live in `com.r35157.nenjim.component`; the cano
|
|||||||
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
|
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
|
||||||
|
|
||||||
### Requirement: Deferred runtime features remain absent
|
### Requirement: Deferred runtime features remain absent
|
||||||
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. Except for the Process Manager's read-only polling of the existing started-process snapshot, the Hub SHALL not add configurable autorun, lifecycle events, subscriptions, process controls, stop, restart, upgrade, health monitoring, recovery, or context-bound dependency access.
|
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. Hub autorun configuration SHALL remain a private startup input that selects only the implementation's known process constructors in explicit manual order. Except for that selection and the Process Manager's read-only polling of the existing started-process snapshot, the Hub SHALL not add lifecycle events, subscriptions, process controls, stop, restart, upgrade, health monitoring, recovery, automatic dependency construction, or context-bound dependency access.
|
||||||
|
|
||||||
#### Scenario: Consumer inspects the first Registry API
|
#### Scenario: Consumer inspects the first Registry API
|
||||||
- **WHEN** a consumer examines the public Registry, Registry-manager, process, Hub, and Process Manager contracts
|
- **WHEN** a consumer examines the public Registry, Registry-manager, process, Hub, and Process Manager contracts
|
||||||
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, Hub startup/snapshots, and the Process Manager role are available and no deferred runtime feature is exposed
|
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, Hub startup/snapshots, and the Process Manager role are available and configuration, discovery, resolution, and control remain absent from public APIs
|
||||||
|
|
||||||
### Requirement: Components and processes have minimal orthogonal contracts
|
### Requirement: Components and processes have minimal orthogonal contracts
|
||||||
`NenjimComponent` SHALL remain a pure marker with no component-ID or lifecycle property. The one canonical `com.r35157.nenjim.process.NenjimProcess` SHALL extend `NenjimComponent` and SHALL expose only `start() throws Exception` as its common lifecycle operation. Service and application SHALL remain independent domain or orchestration roles: an implementation in either role SHALL additionally implement `NenjimProcess` only when it has a Hub-managed active startup lifecycle. Passive components SHALL not implement `NenjimProcess`, and no `NenjimApplication` compatibility type SHALL remain.
|
`NenjimComponent` SHALL remain a pure marker with no component-ID or lifecycle property. The one canonical `com.r35157.nenjim.process.NenjimProcess` SHALL extend `NenjimComponent` and SHALL expose only `start() throws Exception` as its common lifecycle operation. Service and application SHALL remain independent domain or orchestration roles: an implementation in either role SHALL additionally implement `NenjimProcess` only when it has a Hub-managed active startup lifecycle. Passive components SHALL not implement `NenjimProcess`, and no `NenjimApplication` compatibility type SHALL remain.
|
||||||
@@ -186,63 +186,63 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
|||||||
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
||||||
|
|
||||||
### Requirement: NenjimHub owns composition and runtime startup
|
### Requirement: NenjimHub owns composition and runtime startup
|
||||||
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the Journal subsystem as `nenjim.journal.service`, start Journal second, and continue to the complete hardcoded business component set from issue #76 only after Journal startup succeeds. During application composition the Hub SHALL construct exactly one `JavaFxRuntimeServiceImpl`, register it as `nenjim.javafx-runtime.service`, inject its service view into Mission Control and Process Manager, construct the Process Manager with itself, and register that manager as `nenjim.process-manager.default`. The Hub SHALL resolve only explicitly enabled IDs through `NenjimProcess`, start the JavaFX runtime service after enabled non-JavaFX business processes and before Mission Control and Process Manager, start Process Manager last, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup, and the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
`NenjimHub` SHALL remain the global runtime owner exposing only `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither component nor process and SHALL not register itself. Its only accepted startup attempt SHALL validate the complete fixed-path Hub configuration before starting anything, construct and start Registry first, register Registry as `nenjim.registry.service`, record it, construct and register the complete passive graph, then lazily construct, register, start, and record only the remaining configured processes in exact order beginning with Journal. Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub. Registration SHALL never imply startup. The Hub SHALL preserve the online point and blocking wait only after all configured processes succeed, without retry, rollback, dependency inference, or reordering.
|
||||||
|
|
||||||
#### Scenario: Registry starts before graph construction
|
#### Scenario: Registry starts before passive graph construction
|
||||||
- **WHEN** the Hub performs its first startup attempt
|
- **WHEN** the Hub has validated a complete startup plan and performs its first startup attempt
|
||||||
- **THEN** it starts, registers, and records Registry first before constructing Journal or components in the business graph
|
- **THEN** it starts, registers, and records Registry first before constructing the passive component graph or any selected non-Registry process
|
||||||
|
|
||||||
#### Scenario: Journal starts before business composition
|
#### Scenario: Journal follows passive composition but starts second
|
||||||
- **WHEN** Registry startup and self-registration have succeeded
|
- **WHEN** Registry bootstrap and passive graph construction have succeeded
|
||||||
- **THEN** the Hub registers, starts, and records Journal before constructing components in the business graph
|
- **THEN** the Hub constructs, registers, starts, and records Journal before any later configured process, so Journal remains the second successful process
|
||||||
|
|
||||||
#### Scenario: Registry is process-discoverable
|
#### Scenario: Registry is process-discoverable
|
||||||
- **WHEN** the Registry registration has completed
|
- **WHEN** the Registry registration has completed
|
||||||
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||||
|
|
||||||
#### Scenario: Journal is process-discoverable
|
#### Scenario: Journal is process-discoverable
|
||||||
- **WHEN** the Journal registration has completed
|
- **WHEN** the configured Journal construction and registration have completed
|
||||||
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||||
|
|
||||||
#### Scenario: JavaFX runtime is process-discoverable
|
#### Scenario: Selected JavaFX runtime is process-discoverable
|
||||||
- **WHEN** Hub-owned application composition has completed
|
- **WHEN** `nenjim.javafx-runtime.service` is selected and its registration has completed
|
||||||
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under `nenjim.javafx-runtime.service`
|
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under that ID
|
||||||
|
|
||||||
#### Scenario: JavaFX applications share the runtime
|
#### Scenario: Selected JavaFX applications share the runtime
|
||||||
- **WHEN** Hub-owned application composition has completed
|
- **WHEN** JavaFX runtime precedes selected Mission Control or Process Manager entries
|
||||||
- **THEN** Mission Control and Process Manager retain the same `JavaFxRuntimeService` instance registered by the Hub
|
- **THEN** each selected application retains the same already-constructed `JavaFxRuntimeService` instance registered by the Hub
|
||||||
|
|
||||||
#### Scenario: Process Manager is process-discoverable
|
#### Scenario: Selected Process Manager is process-discoverable
|
||||||
- **WHEN** Hub-owned application composition has completed
|
- **WHEN** `nenjim.process-manager.default` is selected after JavaFX runtime and its construction and registration complete
|
||||||
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess` under `nenjim.process-manager.default`
|
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess`
|
||||||
|
|
||||||
#### Scenario: Required hardcoded bindings are registered
|
#### Scenario: Required passive bindings are registered
|
||||||
- **WHEN** Hub composition completes
|
- **WHEN** Hub passive composition completes
|
||||||
- **THEN** Journal, the JavaFX runtime, the Process Manager, and all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding both service managers
|
- **THEN** all current passive component IDs and implementation choices from issue #76 are present exactly as configured, while process IDs other than Registry remain absent until selected
|
||||||
|
|
||||||
#### Scenario: Explicit startup subset preserves order
|
#### Scenario: Configured startup subset preserves order
|
||||||
- **WHEN** Hub startup succeeds through the Process Manager phase
|
- **WHEN** Hub startup completes successfully
|
||||||
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
- **THEN** successful process IDs exactly equal the validated `[AUTO_START]` entries in their declared order
|
||||||
|
|
||||||
#### Scenario: Deliberately inactive processes remain inactive
|
#### Scenario: Omitted processes do not enter the Registry
|
||||||
- **WHEN** Hub startup reaches its online point
|
- **WHEN** a modern process ID is omitted from `[AUTO_START]`
|
||||||
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
|
- **THEN** that process is neither constructed, registered, started, nor recorded even though its implementation is known to the Hub
|
||||||
|
|
||||||
#### Scenario: Journal failure is fail-fast
|
#### Scenario: Journal failure is fail-fast
|
||||||
- **WHEN** Journal process startup throws
|
- **WHEN** Journal construction, registration, or process startup throws
|
||||||
- **THEN** Registry remains the only recorded process, no business component is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
- **THEN** Registry remains the only recorded process, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
#### Scenario: JavaFX runtime failure is fail-fast
|
#### Scenario: JavaFX runtime failure is fail-fast
|
||||||
- **WHEN** JavaFX runtime service startup throws
|
- **WHEN** selected JavaFX runtime construction, registration, or startup throws
|
||||||
- **THEN** its ID is not recorded, Mission Control and Process Manager do not start, the exception propagates, and the Hub neither announces online nor enters its final wait
|
- **THEN** its ID is not recorded, no later configured process is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||||
|
|
||||||
#### Scenario: Hub start is not retried
|
#### Scenario: Hub start is not retried
|
||||||
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||||
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||||
|
|
||||||
#### Scenario: Startup failure is fail-fast
|
#### Scenario: Startup failure is fail-fast
|
||||||
- **WHEN** any process startup throws
|
- **WHEN** any passive composition, selected construction, registration, or process startup operation throws
|
||||||
- **THEN** the exception propagates, no later process starts, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
- **THEN** the exception propagates, no later process is constructed or started, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||||
|
|
||||||
### Requirement: Started-process snapshots are ordered immutable and truthful
|
### Requirement: Started-process snapshots are ordered immutable and truthful
|
||||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# nenjim-hub-configuration Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines the mandatory deployment configuration that selects which modern Nenjim processes the Hub constructs and starts, while preserving explicit order and fail-fast lifecycle semantics.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: Hub startup requires one fixed runtime configuration
|
||||||
|
The reference Hub SHALL read only `conf/NenjimHub.conf` and SHALL complete all configuration parsing and validation before starting Registry or any other process. The path SHALL not be configurable through `Main`, command-line arguments, or the public `NenjimHub` API. A missing, unreadable, or non-regular runtime file SHALL fail Hub startup clearly and identify the configured path. The Hub SHALL not read, copy, generate, or fall back to `conf/NenjimHub.conf.example`, and SHALL not fall back to a hardcoded startup list.
|
||||||
|
|
||||||
|
#### Scenario: Runtime configuration is valid
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is a readable regular file containing a valid format 1 configuration
|
||||||
|
- **THEN** the Hub derives its complete requested process order from that file before starting Registry
|
||||||
|
|
||||||
|
#### Scenario: Runtime configuration cannot be read
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is missing, unreadable, or not a regular file
|
||||||
|
- **THEN** startup fails with a diagnostic identifying `conf/NenjimHub.conf` before any process starts
|
||||||
|
|
||||||
|
#### Scenario: Only the example exists
|
||||||
|
- **WHEN** `conf/NenjimHub.conf` is absent but `conf/NenjimHub.conf.example` exists
|
||||||
|
- **THEN** startup fails without reading, copying, or using the example and without starting a hardcoded fallback set
|
||||||
|
|
||||||
|
### Requirement: Format 1 has one exact ordered AUTO_START section
|
||||||
|
The first actual configuration entry SHALL be exactly `FORMAT_VERSION=1`, after optional leading blank lines and full-line comments. Version 1 SHALL be the only supported version; a missing, malformed, duplicate, version 2, or otherwise unsupported declaration SHALL fail. The next actual entry SHALL be exactly one `[AUTO_START]` header, optionally followed only by a trailing `#` comment. Unknown, malformed, repeated, valued, or out-of-order sections and entries before `[AUTO_START]` SHALL fail.
|
||||||
|
|
||||||
|
Outside `[AUTO_START]`, leading and trailing whitespace SHALL be ignored, a line whose trimmed content begins with `#` SHALL be a full-line comment, and `#` after actual content SHALL begin a trailing comment. Inside `[AUTO_START]`, surrounding whitespace around a process ID SHALL be ignored; full-line and trailing comments SHALL be ignored; comment-only lines SHALL not terminate the section; and the first whitespace-only line or end of file SHALL terminate it. After termination, only blank lines and comments SHALL be accepted. Format 1 SHALL provide no quoting or escaping for `#`.
|
||||||
|
|
||||||
|
#### Scenario: Leading comments and trailing comments are accepted
|
||||||
|
- **WHEN** blank lines or full-line comments precede `FORMAT_VERSION=1`, and the version, section header, or process entries contain trailing `#` comments
|
||||||
|
- **THEN** comment text and permitted surrounding whitespace are ignored while the actual entries are parsed normally
|
||||||
|
|
||||||
|
#### Scenario: Comment inside AUTO_START does not terminate it
|
||||||
|
- **WHEN** a comment-only line appears between two process IDs in `[AUTO_START]`
|
||||||
|
- **THEN** both process IDs remain part of the ordered section
|
||||||
|
|
||||||
|
#### Scenario: Empty line terminates AUTO_START
|
||||||
|
- **WHEN** a whitespace-only line appears after at least one `[AUTO_START]` entry
|
||||||
|
- **THEN** the section terminates at that line and later actual content fails as unexpected content
|
||||||
|
|
||||||
|
#### Scenario: End of file terminates AUTO_START
|
||||||
|
- **WHEN** the final process ID is immediately followed by end of file
|
||||||
|
- **THEN** the section terminates successfully at end of file
|
||||||
|
|
||||||
|
#### Scenario: Version declaration is invalid
|
||||||
|
- **WHEN** the version is absent, malformed, duplicated, not the first actual entry, or has a value other than `1`
|
||||||
|
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line and reason
|
||||||
|
|
||||||
|
#### Scenario: Section structure is invalid
|
||||||
|
- **WHEN** `[AUTO_START]` is absent, duplicated, misspelled, has a value or non-comment trailing token, follows another actual entry, or is replaced by an unknown section
|
||||||
|
- **THEN** configuration validation fails before any process starts and identifies the path, relevant line, offending content and reason
|
||||||
|
|
||||||
|
### Requirement: The complete requested process plan is validated before startup
|
||||||
|
Every active `[AUTO_START]` line SHALL be parsed through the canonical `NenjimComponentId` rules and retained in source order with its line location. Before Registry starts, validation SHALL reject invalid ID syntax, duplicate IDs, unknown IDs, passive-only component IDs, and IDs the reference Hub cannot construct as canonical `NenjimProcess` objects. The section SHALL contain Registry exactly once at position one and Journal exactly once at position two. Applicable failures SHALL identify `conf/NenjimHub.conf`, the relevant line, offending value, and reason.
|
||||||
|
|
||||||
|
#### Scenario: Process ID is malformed
|
||||||
|
- **WHEN** an active section line is not a valid `NenjimComponentId`
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the path, line, offending value and component-ID reason
|
||||||
|
|
||||||
|
#### Scenario: Process ID is duplicated
|
||||||
|
- **WHEN** the same canonical process ID appears more than once
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the duplicate value and line
|
||||||
|
|
||||||
|
#### Scenario: ID is not constructible as a process
|
||||||
|
- **WHEN** an entry is unknown, identifies only a passive component, or is not in the Hub's modern process-construction catalogue
|
||||||
|
- **THEN** validation fails before Registry startup rather than constructing or starting any process
|
||||||
|
|
||||||
|
#### Scenario: Bootstrap entries are not first and second
|
||||||
|
- **WHEN** Registry is absent or not first, or Journal is absent or not second
|
||||||
|
- **THEN** validation fails before Registry startup and identifies the required placement
|
||||||
|
|
||||||
|
### Requirement: Registry bootstraps eager passive composition
|
||||||
|
After validating the entire plan, the Hub SHALL construct the configured Registry subsystem, start Registry, register its service and process object as `nenjim.registry.service`, and record that ID only after successful startup. The Hub SHALL then eagerly construct and register the complete current passive component graph needed by selectable processes. This passive phase SHALL not construct any canonical `NenjimProcess`, start background workers, initialize JavaFX, add startup IDs, infer process dependencies, or implicitly create an omitted process. Plain internal dependencies that are not `NenjimComponent` objects MAY be constructed as part of passive composition.
|
||||||
|
|
||||||
|
#### Scenario: Registry surrounds passive composition
|
||||||
|
- **WHEN** a valid Hub startup begins
|
||||||
|
- **THEN** Registry starts, is registered, and is recorded before the complete passive component graph is constructed and registered
|
||||||
|
|
||||||
|
#### Scenario: Passive composition completes
|
||||||
|
- **WHEN** Registry bootstrap succeeds
|
||||||
|
- **THEN** all current non-process building blocks, adapters, and services are constructed and registered without starting a process
|
||||||
|
|
||||||
|
#### Scenario: Passive construction fails
|
||||||
|
- **WHEN** any passive component cannot be constructed or registered
|
||||||
|
- **THEN** startup stops immediately with Registry remaining recorded and no configured process after Registry constructed or started
|
||||||
|
|
||||||
|
### Requirement: Selected processes are constructed and started lazily in exact order
|
||||||
|
The Hub SHALL support the modern process IDs `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `jupiter-perps-alarm.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default` through implementation-only construction capability that creates no process instance during validation. After passive composition, the Hub SHALL visit every configured entry after Registry in declared order, beginning with Journal. At each entry it SHALL construct only that process and any process-specific passive administration view, register the process under the exact configured ID, resolve it through the canonical `NenjimProcess` contract where applicable, call `start()`, and record the ID only after successful return.
|
||||||
|
|
||||||
|
The configuration SHALL be the complete manually ordered process dependency closure. The Hub SHALL not discover, insert, recursively construct, reorder, or automatically start dependencies. A process omitted from `[AUTO_START]` SHALL not be constructed, registered, started, or recorded, even when its implementation is available. Legacy process prototypes outside the modern Hub graph SHALL not be selectable.
|
||||||
|
|
||||||
|
#### Scenario: Configured process starts successfully
|
||||||
|
- **WHEN** a configured process entry is reached and its dependencies are already available
|
||||||
|
- **THEN** that process is constructed, registered, started, and recorded before the next entry is reached
|
||||||
|
|
||||||
|
#### Scenario: Process is omitted
|
||||||
|
- **WHEN** a known process ID does not appear in `[AUTO_START]`
|
||||||
|
- **THEN** no instance of that process is constructed or registered and its lifecycle is never started or recorded
|
||||||
|
|
||||||
|
#### Scenario: Jupiter Perps Alarm is selected
|
||||||
|
- **WHEN** `jupiter-perps-alarm.default` is explicitly listed after its required predecessors
|
||||||
|
- **THEN** the otherwise inactive modern alarm process is constructed, registered, started and recorded at that exact position
|
||||||
|
|
||||||
|
#### Scenario: Dependency order is invalid
|
||||||
|
- **WHEN** a selected process is reached before a process dependency required by its current constructor or startup behavior
|
||||||
|
- **THEN** construction or startup fails clearly without implicit dependency construction, repair, or reordering
|
||||||
|
|
||||||
|
#### Scenario: JavaFX processes are omitted
|
||||||
|
- **WHEN** a valid headless plan omits `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||||
|
- **THEN** none of those objects is constructed or registered and the JavaFX toolkit remains uninitialized by the Hub
|
||||||
|
|
||||||
|
### Requirement: Configured startup is fail-fast and reports actual milestones
|
||||||
|
Registry and every selected process SHALL be registered before its `start()` call and appended to the Hub's immutable ordered startup snapshot only after `start()` returns successfully. Construction failure SHALL leave the selected process unregistered. Registration or startup failure SHALL stop the plan before any later entry is constructed or started; a startup failure MAY leave that process registered but SHALL not record it. Earlier successful processes SHALL remain in snapshots, no rollback or retry SHALL occur, and the Hub SHALL announce its existing online point and enter its blocking runtime wait only after every configured process succeeds.
|
||||||
|
|
||||||
|
#### Scenario: Selected construction fails
|
||||||
|
- **WHEN** construction of the current selected process throws
|
||||||
|
- **THEN** the process is not registered or recorded, no later process is constructed, and the Hub does not announce online or enter its final wait
|
||||||
|
|
||||||
|
#### Scenario: Selected registration fails
|
||||||
|
- **WHEN** registration of the constructed current process throws
|
||||||
|
- **THEN** the process is not started or recorded, no later process is constructed, and no rollback is attempted
|
||||||
|
|
||||||
|
#### Scenario: Selected startup fails
|
||||||
|
- **WHEN** the current process's `start()` throws
|
||||||
|
- **THEN** it remains unrecorded, no later process is constructed or started, earlier successful IDs remain visible, and no rollback is attempted
|
||||||
|
|
||||||
|
#### Scenario: Configured startup completes
|
||||||
|
- **WHEN** all configured process starts return successfully
|
||||||
|
- **THEN** the startup snapshot has the exact configured order, the online point is announced, and the Hub enters its existing blocking wait
|
||||||
|
|
||||||
|
### Requirement: The version-controlled example is a copy-only template
|
||||||
|
The repository SHALL contain `conf/NenjimHub.conf.example` as a documented format 1 copy-only template with only the mandatory Registry and Journal entries active. It SHALL contain no optional deployment-specific process IDs. Its comments SHALL state that the file must be copied to `conf/NenjimHub.conf` before use, Registry and Journal are mandatory, order is preserved, and process dependencies must be listed manually before their consumers. Documentation SHALL show the same minimal bootstrap as a headless example and SHALL describe recursive dependency construction as future work rather than implemented behavior.
|
||||||
|
|
||||||
|
#### Scenario: Example provides a generic minimal bootstrap
|
||||||
|
- **WHEN** the example's uncommented process IDs are read in order
|
||||||
|
- **THEN** they are only Registry and Journal, with no optional deployment-specific process ID present
|
||||||
|
|
||||||
|
#### Scenario: Operator uses the minimal headless example
|
||||||
|
- **WHEN** the documented headless configuration is copied to the runtime path
|
||||||
|
- **THEN** it selects only Registry and Journal and leaves every optional process for the operator to add explicitly
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.r35157.nenjim.hubd.impl.ref;
|
||||||
|
|
||||||
|
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/** Immutable, validated process-start plan for one Hub startup attempt. */
|
||||||
|
record NenjimHubConfiguration(
|
||||||
|
List<NenjimComponentId> autoStartProcessIds) {
|
||||||
|
|
||||||
|
NenjimHubConfiguration {
|
||||||
|
Objects.requireNonNull(
|
||||||
|
autoStartProcessIds,
|
||||||
|
"autoStartProcessIds");
|
||||||
|
autoStartProcessIds.forEach(componentId ->
|
||||||
|
Objects.requireNonNull(componentId, "autoStartProcessId"));
|
||||||
|
autoStartProcessIds = List.copyOf(autoStartProcessIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
package com.r35157.nenjim.hubd.impl.ref;
|
||||||
|
|
||||||
|
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** Strict parser for the implementation-only Nenjim Hub configuration. */
|
||||||
|
final class NenjimHubConfigurationParser {
|
||||||
|
|
||||||
|
static NenjimHubConfiguration parse(
|
||||||
|
Path path,
|
||||||
|
Set<NenjimComponentId> knownProcessIds,
|
||||||
|
NenjimComponentId registryProcessId,
|
||||||
|
NenjimComponentId journalProcessId) throws IOException {
|
||||||
|
Objects.requireNonNull(path, "path");
|
||||||
|
Objects.requireNonNull(knownProcessIds, "knownProcessIds");
|
||||||
|
Objects.requireNonNull(registryProcessId, "registryProcessId");
|
||||||
|
Objects.requireNonNull(journalProcessId, "journalProcessId");
|
||||||
|
|
||||||
|
List<String> lines = readConfiguration(path);
|
||||||
|
List<ParsedProcess> parsedProcesses = parseLines(path, lines);
|
||||||
|
validateProcessPlan(
|
||||||
|
path,
|
||||||
|
parsedProcesses,
|
||||||
|
Set.copyOf(knownProcessIds),
|
||||||
|
registryProcessId,
|
||||||
|
journalProcessId);
|
||||||
|
|
||||||
|
return new NenjimHubConfiguration(
|
||||||
|
parsedProcesses.stream()
|
||||||
|
.map(ParsedProcess::componentId)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> readConfiguration(Path path)
|
||||||
|
throws IOException {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
throw new IOException(
|
||||||
|
path + ": Nenjim Hub runtime configuration is missing");
|
||||||
|
}
|
||||||
|
if (!Files.isRegularFile(path)) {
|
||||||
|
throw new IOException(
|
||||||
|
path + ": Nenjim Hub runtime configuration is not a "
|
||||||
|
+ "regular file");
|
||||||
|
}
|
||||||
|
if (!Files.isReadable(path)) {
|
||||||
|
throw new IOException(
|
||||||
|
path + ": Nenjim Hub runtime configuration is not "
|
||||||
|
+ "readable");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Files.readAllLines(path, StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new IOException(
|
||||||
|
path + ": Failed to read Nenjim Hub runtime "
|
||||||
|
+ "configuration",
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ParsedProcess> parseLines(
|
||||||
|
Path path,
|
||||||
|
List<String> lines) {
|
||||||
|
ParserState state = ParserState.EXPECT_FORMAT_VERSION;
|
||||||
|
List<ParsedProcess> parsedProcesses = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
|
||||||
|
String sourceLine = lines.get(lineIndex);
|
||||||
|
String trimmedSourceLine = sourceLine.strip();
|
||||||
|
boolean blankLine = trimmedSourceLine.isEmpty();
|
||||||
|
boolean commentOnlyLine = trimmedSourceLine.startsWith("#");
|
||||||
|
String content = stripTrailingComment(sourceLine).strip();
|
||||||
|
int lineNumber = lineIndex + 1;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case EXPECT_FORMAT_VERSION -> {
|
||||||
|
if (blankLine || commentOnlyLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parseFormatVersion(path, lineNumber, content);
|
||||||
|
state = ParserState.EXPECT_AUTO_START;
|
||||||
|
}
|
||||||
|
case EXPECT_AUTO_START -> {
|
||||||
|
if (blankLine || commentOnlyLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rejectAdditionalFormatVersion(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content);
|
||||||
|
if (!AUTO_START_HEADER.equals(content)) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Expected " + AUTO_START_HEADER
|
||||||
|
+ " as the first actual entry after "
|
||||||
|
+ FORMAT_VERSION_DECLARATION);
|
||||||
|
}
|
||||||
|
state = ParserState.IN_AUTO_START;
|
||||||
|
}
|
||||||
|
case IN_AUTO_START -> {
|
||||||
|
if (blankLine) {
|
||||||
|
state = ParserState.AFTER_AUTO_START;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (commentOnlyLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rejectAdditionalFormatVersion(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content);
|
||||||
|
rejectSectionHeader(path, lineNumber, content);
|
||||||
|
parsedProcesses.add(parseProcessId(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content));
|
||||||
|
}
|
||||||
|
case AFTER_AUTO_START -> {
|
||||||
|
if (blankLine || commentOnlyLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rejectAdditionalFormatVersion(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content);
|
||||||
|
rejectSectionHeader(path, lineNumber, content);
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Unexpected content after "
|
||||||
|
+ AUTO_START_HEADER + " terminated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == ParserState.EXPECT_FORMAT_VERSION) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
path + ": Missing " + FORMAT_VERSION_DECLARATION
|
||||||
|
+ " as the first actual configuration entry");
|
||||||
|
}
|
||||||
|
if (state == ParserState.EXPECT_AUTO_START) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
path + ": Missing required " + AUTO_START_HEADER
|
||||||
|
+ " section");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedProcesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseFormatVersion(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String content) {
|
||||||
|
Matcher matcher = FORMAT_VERSION_PATTERN.matcher(content);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
String reason = content.startsWith(FORMAT_VERSION_KEY)
|
||||||
|
? "Malformed FORMAT_VERSION; expected exactly "
|
||||||
|
+ FORMAT_VERSION_DECLARATION
|
||||||
|
: FORMAT_VERSION_DECLARATION
|
||||||
|
+ " must be the first actual configuration entry";
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
int version;
|
||||||
|
try {
|
||||||
|
version = Integer.parseInt(matcher.group(1));
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Malformed FORMAT_VERSION; expected "
|
||||||
|
+ FORMAT_VERSION_DECLARATION,
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version != SUPPORTED_FORMAT_VERSION) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Unsupported FORMAT_VERSION '" + version
|
||||||
|
+ "'; only version "
|
||||||
|
+ SUPPORTED_FORMAT_VERSION + " is supported");
|
||||||
|
}
|
||||||
|
if (!FORMAT_VERSION_DECLARATION.equals(content)) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Malformed FORMAT_VERSION; expected exactly "
|
||||||
|
+ FORMAT_VERSION_DECLARATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void rejectAdditionalFormatVersion(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String content) {
|
||||||
|
if (content.startsWith(FORMAT_VERSION_KEY)) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Duplicate or misplaced FORMAT_VERSION declaration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void rejectSectionHeader(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String content) {
|
||||||
|
if (AUTO_START_HEADER.equals(content)) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Duplicate " + AUTO_START_HEADER + " section");
|
||||||
|
}
|
||||||
|
if (content.startsWith("[") || content.endsWith("]")) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Unknown or malformed section; only "
|
||||||
|
+ AUTO_START_HEADER + " is supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParsedProcess parseProcessId(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String content) {
|
||||||
|
try {
|
||||||
|
return new ParsedProcess(
|
||||||
|
new NenjimComponentId(content),
|
||||||
|
lineNumber);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
lineNumber,
|
||||||
|
content,
|
||||||
|
"Invalid Nenjim process ID: "
|
||||||
|
+ exception.getMessage(),
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateProcessPlan(
|
||||||
|
Path path,
|
||||||
|
List<ParsedProcess> parsedProcesses,
|
||||||
|
Set<NenjimComponentId> knownProcessIds,
|
||||||
|
NenjimComponentId registryProcessId,
|
||||||
|
NenjimComponentId journalProcessId) {
|
||||||
|
Map<NenjimComponentId, Integer> firstLineById = new HashMap<>();
|
||||||
|
|
||||||
|
for (ParsedProcess parsedProcess : parsedProcesses) {
|
||||||
|
NenjimComponentId componentId = parsedProcess.componentId();
|
||||||
|
Integer firstLine = firstLineById.putIfAbsent(
|
||||||
|
componentId,
|
||||||
|
parsedProcess.lineNumber());
|
||||||
|
if (firstLine != null) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
parsedProcess.lineNumber(),
|
||||||
|
componentId.value(),
|
||||||
|
"Duplicate process ID; first declared on line "
|
||||||
|
+ firstLine);
|
||||||
|
}
|
||||||
|
if (!knownProcessIds.contains(componentId)) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
parsedProcess.lineNumber(),
|
||||||
|
componentId.value(),
|
||||||
|
"ID does not identify a modern Nenjim process "
|
||||||
|
+ "constructible by this Hub");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requireProcessAtPosition(
|
||||||
|
path,
|
||||||
|
parsedProcesses,
|
||||||
|
registryProcessId,
|
||||||
|
0,
|
||||||
|
"Registry must be the first AUTO_START process");
|
||||||
|
requireProcessAtPosition(
|
||||||
|
path,
|
||||||
|
parsedProcesses,
|
||||||
|
journalProcessId,
|
||||||
|
1,
|
||||||
|
"Journal must be the second AUTO_START process");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void requireProcessAtPosition(
|
||||||
|
Path path,
|
||||||
|
List<ParsedProcess> parsedProcesses,
|
||||||
|
NenjimComponentId requiredId,
|
||||||
|
int requiredIndex,
|
||||||
|
String reason) {
|
||||||
|
if (parsedProcesses.size() <= requiredIndex) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
path + ": Missing required process ID '"
|
||||||
|
+ requiredId.value() + "'; " + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedProcess actual = parsedProcesses.get(requiredIndex);
|
||||||
|
if (!requiredId.equals(actual.componentId())) {
|
||||||
|
throw invalidLine(
|
||||||
|
path,
|
||||||
|
actual.lineNumber(),
|
||||||
|
actual.componentId().value(),
|
||||||
|
reason + "; found '"
|
||||||
|
+ actual.componentId().value() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stripTrailingComment(String line) {
|
||||||
|
int commentStart = line.indexOf('#');
|
||||||
|
return commentStart < 0 ? line : line.substring(0, commentStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IllegalArgumentException invalidLine(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String value,
|
||||||
|
String reason) {
|
||||||
|
return invalidLine(path, lineNumber, value, reason, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IllegalArgumentException invalidLine(
|
||||||
|
Path path,
|
||||||
|
int lineNumber,
|
||||||
|
String value,
|
||||||
|
String reason,
|
||||||
|
Exception cause) {
|
||||||
|
return new IllegalArgumentException(
|
||||||
|
path + ":" + lineNumber + ": Invalid Nenjim Hub "
|
||||||
|
+ "configuration value '" + value + "': " + reason,
|
||||||
|
cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ParsedProcess(
|
||||||
|
NenjimComponentId componentId,
|
||||||
|
int lineNumber) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ParserState {
|
||||||
|
EXPECT_FORMAT_VERSION,
|
||||||
|
EXPECT_AUTO_START,
|
||||||
|
IN_AUTO_START,
|
||||||
|
AFTER_AUTO_START
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SUPPORTED_FORMAT_VERSION = 1;
|
||||||
|
private static final String FORMAT_VERSION_KEY = "FORMAT_VERSION";
|
||||||
|
private static final String FORMAT_VERSION_DECLARATION =
|
||||||
|
FORMAT_VERSION_KEY + "=" + SUPPORTED_FORMAT_VERSION;
|
||||||
|
private static final String AUTO_START_HEADER = "[AUTO_START]";
|
||||||
|
private static final Pattern FORMAT_VERSION_PATTERN =
|
||||||
|
Pattern.compile(FORMAT_VERSION_KEY + "=(\\d+)");
|
||||||
|
|
||||||
|
private NenjimHubConfigurationParser() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,6 @@ import com.r35157.nenjim.hubd.NenjimHub;
|
|||||||
import com.r35157.nenjim.npm.NenjimProcessManager;
|
import com.r35157.nenjim.npm.NenjimProcessManager;
|
||||||
import com.r35157.nenjim.npm.impl.ref.NenjimProcessManagerImpl;
|
import com.r35157.nenjim.npm.impl.ref.NenjimProcessManagerImpl;
|
||||||
import com.r35157.nenjim.process.NenjimProcess;
|
import com.r35157.nenjim.process.NenjimProcess;
|
||||||
import com.r35157.nenjim.service.journal.JournalService;
|
|
||||||
import com.r35157.nenjim.service.journal.JournalServiceManager;
|
import com.r35157.nenjim.service.journal.JournalServiceManager;
|
||||||
import com.r35157.nenjim.service.journal.impl.ref.JournalServiceAssembly;
|
import com.r35157.nenjim.service.journal.impl.ref.JournalServiceAssembly;
|
||||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||||
@@ -58,7 +57,10 @@ import java.nio.file.Path;
|
|||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@@ -74,10 +76,19 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
"NenjimHub start has already been attempted");
|
"NenjimHub start has already been attempted");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Initializing Nenjim Registry and components...");
|
Map<NenjimComponentId, ProcessConstructor> processConstructors =
|
||||||
|
createProcessConstructorCatalogue();
|
||||||
|
NenjimHubConfiguration configuration =
|
||||||
|
NenjimHubConfigurationParser.parse(
|
||||||
|
HUB_CONFIGURATION_PATH,
|
||||||
|
selectableProcessIds(processConstructors),
|
||||||
|
REGISTRY_SERVICE_ID,
|
||||||
|
JOURNAL_SERVICE_ID);
|
||||||
|
|
||||||
|
log.info("Initializing Nenjim Registry...");
|
||||||
NenjimRegistryAssembly registryAssembly =
|
NenjimRegistryAssembly registryAssembly =
|
||||||
NenjimRegistryAssembly.create();
|
NenjimRegistryAssembly.create();
|
||||||
NenjimRegistryService registryService =
|
registryService =
|
||||||
registryAssembly.registryService();
|
registryAssembly.registryService();
|
||||||
NenjimProcess registryProcess = registryAssembly.registryProcess();
|
NenjimProcess registryProcess = registryAssembly.registryProcess();
|
||||||
registryServiceManager = registryAssembly.registryServiceManager();
|
registryServiceManager = registryAssembly.registryServiceManager();
|
||||||
@@ -87,19 +98,19 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
REGISTRY_SERVICE_ID,
|
REGISTRY_SERVICE_ID,
|
||||||
registryService);
|
registryService);
|
||||||
recordStartedProcess(REGISTRY_SERVICE_ID);
|
recordStartedProcess(REGISTRY_SERVICE_ID);
|
||||||
initializeAndStartJournal(registryServiceManager);
|
|
||||||
initializeAndRegisterComponents(
|
|
||||||
registryService,
|
|
||||||
registryServiceManager);
|
|
||||||
|
|
||||||
log.info("Starting explicitly enabled Nenjim processes...");
|
log.info("Initializing passive Nenjim components...");
|
||||||
startProcess(registryService, TICKER_SERVICE_ID);
|
initializeAndRegisterPassiveComponents(registryServiceManager);
|
||||||
startProcess(registryService, EVELYN_SERVICE_PROD_ID);
|
|
||||||
startProcess(registryService, EVELYN_SERVICE_TEST_ID);
|
log.info("Constructing and starting configured Nenjim processes...");
|
||||||
startProcess(registryService, EVELYN_IOU_BURNER_PROD_ID);
|
for (NenjimComponentId componentId
|
||||||
startProcess(registryService, JAVAFX_RUNTIME_SERVICE_ID);
|
: configuration.autoStartProcessIds().subList(
|
||||||
startProcess(registryService, EVELYN_MISSION_CONTROL_ID);
|
1,
|
||||||
startProcess(registryService, PROCESS_MANAGER_ID);
|
configuration.autoStartProcessIds().size())) {
|
||||||
|
constructRegisterAndStartProcess(
|
||||||
|
componentId,
|
||||||
|
processConstructors);
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("Done - Now online!");
|
System.out.println("Done - Now online!");
|
||||||
|
|
||||||
@@ -112,29 +123,18 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
return List.copyOf(startedProcessIds);
|
return List.copyOf(startedProcessIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAndStartJournal(
|
private NenjimProcess constructJournalProcess() {
|
||||||
NenjimRegistryServiceManager registryServiceManager)
|
|
||||||
throws Exception {
|
|
||||||
JournalServiceAssembly journalAssembly =
|
JournalServiceAssembly journalAssembly =
|
||||||
JournalServiceAssembly.create(JOURNAL_ROOT);
|
JournalServiceAssembly.create(JOURNAL_ROOT);
|
||||||
JournalService journalService = journalAssembly.journalService();
|
|
||||||
NenjimProcess journalProcess = journalAssembly.journalProcess();
|
|
||||||
journalServiceManager = journalAssembly.journalServiceManager();
|
journalServiceManager = journalAssembly.journalServiceManager();
|
||||||
|
return journalAssembly.journalProcess();
|
||||||
registryServiceManager.registerComponent(
|
|
||||||
JOURNAL_SERVICE_ID,
|
|
||||||
journalService);
|
|
||||||
journalProcess.start();
|
|
||||||
recordStartedProcess(JOURNAL_SERVICE_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAndRegisterComponents(
|
private void initializeAndRegisterPassiveComponents(
|
||||||
NenjimRegistryService registryService,
|
|
||||||
NenjimRegistryServiceManager registryServiceManager) {
|
NenjimRegistryServiceManager registryServiceManager) {
|
||||||
initializeBuildingBlocks(registryServiceManager);
|
initializeBuildingBlocks(registryServiceManager);
|
||||||
initializePriceSources(registryServiceManager);
|
initializePriceSources(registryServiceManager);
|
||||||
initializeServices(registryService, registryServiceManager);
|
initializePassiveServices(registryServiceManager);
|
||||||
initializeApplications(registryServiceManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeBuildingBlocks(
|
private void initializeBuildingBlocks(
|
||||||
@@ -199,15 +199,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
raydiumPoolPriceSource);
|
raydiumPoolPriceSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeServices(
|
private void initializePassiveServices(
|
||||||
NenjimRegistryService registryService,
|
|
||||||
NenjimRegistryServiceManager registryServiceManager) {
|
NenjimRegistryServiceManager registryServiceManager) {
|
||||||
tickerService = new TickerServiceImpl(
|
|
||||||
registryService,
|
|
||||||
RAYDIUM_POOL_PRICE_SOURCE_ID
|
|
||||||
);
|
|
||||||
registryServiceManager.registerComponent(TICKER_SERVICE_ID, tickerService);
|
|
||||||
|
|
||||||
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
|
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
|
||||||
cachedSolanaBlockChain,
|
cachedSolanaBlockChain,
|
||||||
solanaWalletEvelynPerpsProd,
|
solanaWalletEvelynPerpsProd,
|
||||||
@@ -226,21 +219,6 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
JUPITER_PERPS_EVELYN_TEST_ID,
|
JUPITER_PERPS_EVELYN_TEST_ID,
|
||||||
jupiterServicePerpsTest);
|
jupiterServicePerpsTest);
|
||||||
|
|
||||||
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
|
|
||||||
solanaWalletEvelynPerpsTest,
|
|
||||||
jupiterServicePerpsTest,
|
|
||||||
"--config=conf/alarms.conf"
|
|
||||||
);
|
|
||||||
registryServiceManager.registerComponent(
|
|
||||||
JUPITER_PERPS_ALARM_ID,
|
|
||||||
jupiterPerpsAlarm);
|
|
||||||
|
|
||||||
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
|
|
||||||
registryServiceManager.registerComponent(EVELYN_SERVICE_PROD_ID, evelynServiceProd);
|
|
||||||
|
|
||||||
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
|
|
||||||
registryServiceManager.registerComponent(EVELYN_SERVICE_TEST_ID, evelynServiceTest);
|
|
||||||
|
|
||||||
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
||||||
registryServiceManager.registerComponent(
|
registryServiceManager.registerComponent(
|
||||||
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
|
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
|
||||||
@@ -254,16 +232,64 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
registryServiceManager.registerComponent(
|
registryServiceManager.registerComponent(
|
||||||
DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
|
DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
|
||||||
discordAssetAZTokenNotificationService);
|
discordAssetAZTokenNotificationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructTickerProcess() {
|
||||||
|
TickerServiceImpl process = new TickerServiceImpl(
|
||||||
|
registryService,
|
||||||
|
RAYDIUM_POOL_PRICE_SOURCE_ID);
|
||||||
|
tickerService = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructJupiterPerpsAlarmProcess() {
|
||||||
|
JupiterPerpsAlarmImpl process = new JupiterPerpsAlarmImpl(
|
||||||
|
solanaWalletEvelynPerpsTest,
|
||||||
|
jupiterServicePerpsTest,
|
||||||
|
"--config=conf/alarms.conf");
|
||||||
|
jupiterPerpsAlarm = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructEvelynProdProcess() {
|
||||||
|
EvelynImpl process = new EvelynImpl(
|
||||||
|
"Production",
|
||||||
|
requireProcessDependency(
|
||||||
|
tickerService,
|
||||||
|
EVELYN_SERVICE_PROD_ID,
|
||||||
|
TICKER_SERVICE_ID),
|
||||||
|
eveUSDT,
|
||||||
|
clockUTC);
|
||||||
|
evelynServiceProd = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructEvelynTestProcess() {
|
||||||
|
EvelynImpl process = new EvelynImpl(
|
||||||
|
"Test",
|
||||||
|
requireProcessDependency(
|
||||||
|
tickerService,
|
||||||
|
EVELYN_SERVICE_TEST_ID,
|
||||||
|
TICKER_SERVICE_ID),
|
||||||
|
eveUSDT,
|
||||||
|
clockUTC);
|
||||||
|
evelynServiceTest = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NenjimProcess constructEvelynIOUBurnerProdProcess() {
|
||||||
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
|
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
|
||||||
|
|
||||||
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
|
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
|
||||||
|
|
||||||
evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
|
EvelynIOUBurnerServiceImpl process = new EvelynIOUBurnerServiceImpl(
|
||||||
solanaWalletEvelynBurnerProd,
|
solanaWalletEvelynBurnerProd,
|
||||||
jupiterSwapService,
|
jupiterSwapService,
|
||||||
solanaBlockChain,
|
solanaBlockChain,
|
||||||
tickerService,
|
requireProcessDependency(
|
||||||
|
tickerService,
|
||||||
|
EVELYN_IOU_BURNER_PROD_ID,
|
||||||
|
TICKER_SERVICE_ID),
|
||||||
cis,
|
cis,
|
||||||
discordAssetAZTokenNotificationService,
|
discordAssetAZTokenNotificationService,
|
||||||
burnTokenCandidates,
|
burnTokenCandidates,
|
||||||
@@ -273,32 +299,109 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
Duration.ofSeconds(10),
|
Duration.ofSeconds(10),
|
||||||
Duration.ofHours(1)
|
Duration.ofHours(1)
|
||||||
);
|
);
|
||||||
registryServiceManager.registerComponent(
|
evelynIOUBurnerServiceProd = process;
|
||||||
EVELYN_IOU_BURNER_PROD_ID,
|
return process;
|
||||||
evelynIOUBurnerServiceProd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeApplications(
|
private NenjimProcess constructJavaFxRuntimeProcess() {
|
||||||
NenjimRegistryServiceManager registryServiceManager) {
|
JavaFxRuntimeServiceImpl process = new JavaFxRuntimeServiceImpl();
|
||||||
javaFxRuntimeService = new JavaFxRuntimeServiceImpl();
|
javaFxRuntimeService = process;
|
||||||
registryServiceManager.registerComponent(
|
return process;
|
||||||
JAVAFX_RUNTIME_SERVICE_ID,
|
}
|
||||||
javaFxRuntimeService);
|
|
||||||
|
|
||||||
missionControl = new EvelynMissionControlImpl(
|
private NenjimProcess constructMissionControlProcess() {
|
||||||
evelynServiceProd,
|
EvelynMissionControlImpl process = new EvelynMissionControlImpl(
|
||||||
evelynServiceTest,
|
requireProcessDependency(
|
||||||
javaFxRuntimeService);
|
evelynServiceProd,
|
||||||
registryServiceManager.registerComponent(
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
EVELYN_MISSION_CONTROL_ID,
|
EVELYN_SERVICE_PROD_ID),
|
||||||
missionControl);
|
requireProcessDependency(
|
||||||
|
evelynServiceTest,
|
||||||
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
|
EVELYN_SERVICE_TEST_ID),
|
||||||
|
requireProcessDependency(
|
||||||
|
javaFxRuntimeService,
|
||||||
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
|
JAVAFX_RUNTIME_SERVICE_ID));
|
||||||
|
missionControl = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
processManager = new NenjimProcessManagerImpl(
|
private NenjimProcess constructProcessManagerProcess() {
|
||||||
|
NenjimProcessManagerImpl process = new NenjimProcessManagerImpl(
|
||||||
this,
|
this,
|
||||||
javaFxRuntimeService);
|
requireProcessDependency(
|
||||||
registryServiceManager.registerComponent(
|
javaFxRuntimeService,
|
||||||
PROCESS_MANAGER_ID,
|
PROCESS_MANAGER_ID,
|
||||||
processManager);
|
JAVAFX_RUNTIME_SERVICE_ID));
|
||||||
|
processManager = process;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void constructRegisterAndStartProcess(
|
||||||
|
NenjimComponentId componentId,
|
||||||
|
Map<NenjimComponentId, ProcessConstructor> processConstructors)
|
||||||
|
throws Exception {
|
||||||
|
ProcessConstructor constructor = processConstructors.get(componentId);
|
||||||
|
if (constructor == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Validated Nenjim process '" + componentId.value()
|
||||||
|
+ "' has no constructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
NenjimProcess process = constructor.construct();
|
||||||
|
registryServiceManager.registerComponent(componentId, process);
|
||||||
|
startProcess(registryService, componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<NenjimComponentId, ProcessConstructor>
|
||||||
|
createProcessConstructorCatalogue() {
|
||||||
|
return Map.ofEntries(
|
||||||
|
Map.entry(JOURNAL_SERVICE_ID, this::constructJournalProcess),
|
||||||
|
Map.entry(TICKER_SERVICE_ID, this::constructTickerProcess),
|
||||||
|
Map.entry(
|
||||||
|
JUPITER_PERPS_ALARM_ID,
|
||||||
|
this::constructJupiterPerpsAlarmProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_SERVICE_PROD_ID,
|
||||||
|
this::constructEvelynProdProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_SERVICE_TEST_ID,
|
||||||
|
this::constructEvelynTestProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_IOU_BURNER_PROD_ID,
|
||||||
|
this::constructEvelynIOUBurnerProdProcess),
|
||||||
|
Map.entry(
|
||||||
|
JAVAFX_RUNTIME_SERVICE_ID,
|
||||||
|
this::constructJavaFxRuntimeProcess),
|
||||||
|
Map.entry(
|
||||||
|
EVELYN_MISSION_CONTROL_ID,
|
||||||
|
this::constructMissionControlProcess),
|
||||||
|
Map.entry(
|
||||||
|
PROCESS_MANAGER_ID,
|
||||||
|
this::constructProcessManagerProcess));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<NenjimComponentId> selectableProcessIds(
|
||||||
|
Map<NenjimComponentId, ProcessConstructor> processConstructors) {
|
||||||
|
Set<NenjimComponentId> processIds =
|
||||||
|
new HashSet<>(processConstructors.keySet());
|
||||||
|
processIds.add(REGISTRY_SERVICE_ID);
|
||||||
|
return Set.copyOf(processIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T requireProcessDependency(
|
||||||
|
T dependency,
|
||||||
|
NenjimComponentId processId,
|
||||||
|
NenjimComponentId dependencyId) {
|
||||||
|
if (dependency == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot construct configured Nenjim process '"
|
||||||
|
+ processId.value() + "': required predecessor '"
|
||||||
|
+ dependencyId.value()
|
||||||
|
+ "' has not been constructed");
|
||||||
|
}
|
||||||
|
return dependency;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startProcess(
|
private void startProcess(
|
||||||
@@ -328,8 +431,15 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
System.out.println("Nenjim is now shut down!");
|
System.out.println("Nenjim is now shut down!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface ProcessConstructor {
|
||||||
|
NenjimProcess construct() throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(
|
private static final Logger log = LoggerFactory.getLogger(
|
||||||
NenjimHubImpl.class);
|
NenjimHubImpl.class);
|
||||||
|
private static final Path HUB_CONFIGURATION_PATH =
|
||||||
|
Path.of("conf/NenjimHub.conf");
|
||||||
private static final NenjimComponentId REGISTRY_SERVICE_ID =
|
private static final NenjimComponentId REGISTRY_SERVICE_ID =
|
||||||
new NenjimComponentId("nenjim.registry.service");
|
new NenjimComponentId("nenjim.registry.service");
|
||||||
private static final NenjimComponentId JOURNAL_SERVICE_ID =
|
private static final NenjimComponentId JOURNAL_SERVICE_ID =
|
||||||
@@ -386,6 +496,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
|||||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||||
private final AtomicBoolean startAttempted = new AtomicBoolean();
|
private final AtomicBoolean startAttempted = new AtomicBoolean();
|
||||||
private final List<NenjimComponentId> startedProcessIds = new ArrayList<>();
|
private final List<NenjimComponentId> startedProcessIds = new ArrayList<>();
|
||||||
|
private NenjimRegistryService registryService;
|
||||||
private NenjimRegistryServiceManager registryServiceManager;
|
private NenjimRegistryServiceManager registryServiceManager;
|
||||||
private JournalServiceManager journalServiceManager;
|
private JournalServiceManager journalServiceManager;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user