77: Make NenjimHub the runtime owner and replace NenjimApplication with NenjimProcess
This commit is contained in:
@@ -3,24 +3,7 @@
|
||||
## Purpose
|
||||
|
||||
Provide one typed, read-only catalogue of constructed Nenjim components and a lifecycle manager that owns the current hardcoded composition without introducing Context or dynamic-loading semantics.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Components and applications have minimal common contracts
|
||||
`NenjimComponent` SHALL be a pure marker with no component-ID property. `NenjimApplication` SHALL extend `NenjimComponent` and expose only `start() throws Exception` as its common lifecycle operation. Domain service interfaces SHALL remain focused on their domain APIs, while a concrete service implementation with a no-argument active start lifecycle SHALL additionally implement `NenjimApplication`.
|
||||
|
||||
#### Scenario: One object has more than one registration identity
|
||||
- **WHEN** the same component object is registered under two different unused component IDs
|
||||
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
|
||||
|
||||
#### Scenario: A service implementation is startable
|
||||
- **WHEN** a service implementation has a no-argument active start lifecycle
|
||||
- **THEN** the implementation is discoverable as `NenjimApplication` without adding lifecycle operations solely to its domain service interface
|
||||
|
||||
#### Scenario: Common stop is unavailable
|
||||
- **WHEN** a caller uses `NenjimApplication`
|
||||
- **THEN** the contract exposes no common stop operation
|
||||
|
||||
### Requirement: Component IDs are canonical validated values
|
||||
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
|
||||
|
||||
@@ -95,7 +78,7 @@ The internal Registry administration view SHALL remain package-private, SHALL no
|
||||
- **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged
|
||||
|
||||
### Requirement: Registry startup is single-use and gates queries
|
||||
The Registry application's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.
|
||||
The Registry process's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.
|
||||
|
||||
#### Scenario: Query before startup
|
||||
- **WHEN** either lookup operation is called before successful Registry startup
|
||||
@@ -109,33 +92,6 @@ The Registry application's first `start()` call SHALL be the only accepted attem
|
||||
- **WHEN** the first Registry start attempt fails
|
||||
- **THEN** every later start request fails with `IllegalStateException`
|
||||
|
||||
### Requirement: Registry manager owns hardcoded composition and startup
|
||||
`NenjimRegistryServiceManager` SHALL be a Nenjim component whose only public operation is `start() throws Exception`. Its public reference implementation SHALL be a distinct `NenjimApplication` object and SHALL accept only one start attempt, including after failure. The first attempt SHALL construct and directly start one Registry object, register that object first as `nenjim.registry.service`, register the manager second as `nenjim.registry.service-manager`, construct and register the complete required hardcoded component set in dependency order, start only the explicit active application subset in dependency-safe order, announce the existing online point, and preserve the existing blocking wait.
|
||||
|
||||
#### Scenario: Registry and manager views are registered
|
||||
- **WHEN** manager composition reaches completed registration
|
||||
- **THEN** the Registry object is retrievable by one ID as both `NenjimRegistryService` and `NenjimApplication`, the manager is retrievable by one different ID as both `NenjimRegistryServiceManager` and `NenjimApplication`, and the Registry and manager are different instances
|
||||
|
||||
#### Scenario: Required hardcoded bindings are registered
|
||||
- **WHEN** manager composition completes
|
||||
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components
|
||||
|
||||
#### Scenario: Explicit startup subset preserves order
|
||||
- **WHEN** the manager starts applications
|
||||
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
|
||||
|
||||
#### Scenario: Deliberately inactive applications remain inactive
|
||||
- **WHEN** manager startup completes its explicit start sequence
|
||||
- **THEN** `jupiter-perps-alarm.default` and the currently commented Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client are not started
|
||||
|
||||
#### Scenario: Manager start is not retried
|
||||
- **WHEN** any first manager start attempt has begun and start is requested again
|
||||
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||
|
||||
#### Scenario: Registration does not activate components
|
||||
- **WHEN** the manager registers a component
|
||||
- **THEN** registration alone neither starts nor stops that component
|
||||
|
||||
### Requirement: Applications own explicit dependency selection
|
||||
A non-bootstrap application or service that owns a component choice SHALL receive the public Registry view through constructor injection, express the choice as one or more explicit component IDs, and use typed lookup. A required missing or wrong-type binding SHALL fail clearly rather than selecting the first component returned for an interface. The initial Ticker composition SHALL explicitly select only `assetaz.price-source.raydium-pool.eve-usdt`; `assetaz.price-source.hardcoded` SHALL remain registered but inactive.
|
||||
|
||||
@@ -152,18 +108,22 @@ A non-bootstrap application or service that owns a component choice SHALL receiv
|
||||
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
|
||||
|
||||
### Requirement: Main is a thin outer bootstrap
|
||||
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class that does not implement `NenjimApplication`. Its `main(String[])` SHALL construct `NenjimRegistryServiceManagerImpl` through a variable typed as `NenjimApplication` and start it. The Gradle application entry point SHALL name this class. Construction, registration, composition, and lifecycle ownership SHALL remain in `NenjimRegistryServiceManagerImpl`; the bootstrap SHALL perform none of those responsibilities beyond constructing and starting the manager.
|
||||
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class. Its `main(String[])` SHALL construct `com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl` through a variable typed as `NenjimHub` and SHALL call `start()` exactly once. The Gradle application entry point SHALL continue to name this class. The bootstrap SHALL not construct the Registry, manager, or business graph.
|
||||
|
||||
#### Scenario: Start through the outer bootstrap
|
||||
- **WHEN** the configured Java entry point is invoked
|
||||
- **THEN** `Main.main(...)` constructs one Registry manager as a Nenjim application and delegates startup to it
|
||||
- **THEN** `Main.main(...)` constructs one reference Hub through the public Hub interface and delegates startup to it exactly once
|
||||
|
||||
#### Scenario: No duplicate composition path remains
|
||||
- **WHEN** the source tree is inspected after migration
|
||||
- **THEN** the old Hub interface, `com.r35157.nenjim.hubd.impl.ref.Main`, and the `NenjimHubImpl` composition implementation are absent, while `com.r35157.nenjim.hubd.Main` contains no replacement composition path
|
||||
- **THEN** the former Registry-manager composition path is absent, `Main` contains no composition path, and `NenjimHubImpl` is the sole owner of the current hardcoded component graph
|
||||
|
||||
#### Scenario: Bootstrap has no composition responsibility
|
||||
- **WHEN** the outer bootstrap source is inspected
|
||||
- **THEN** composition, process tracking, online announcement, and the blocking wait are absent from `Main` and owned by the Hub implementation
|
||||
|
||||
### Requirement: Registry terminology and package boundaries are consistent
|
||||
Public component contracts SHALL live in `com.r35157.nenjim.component`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; the component ID SHALL live in its `.valuetypes` package; and internal administration and Registry storage SHALL live in `.impl.ref`. Directly affected Ticker price-source API and implementation packages SHALL use `.pricesource` rather than `.plugins.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
||||
Public component contracts SHALL live in `com.r35157.nenjim.component`; the canonical process contract SHALL live in `com.r35157.nenjim.process`; the global Hub contract SHALL live in `com.r35157.nenjim.hubd`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; and the component ID SHALL live in its `.valuetypes` package. Registry storage and its package-private administration view SHALL live in `.impl.ref`, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use `.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
|
||||
|
||||
#### Scenario: Ticker source packages are inspected
|
||||
- **WHEN** source packages and imports are searched after migration
|
||||
@@ -171,11 +131,106 @@ Public component contracts SHALL live in `com.r35157.nenjim.component`; public R
|
||||
|
||||
#### Scenario: Internal Registry administration is inspected
|
||||
- **WHEN** an ordinary consumer compiles against the public Registry packages
|
||||
- **THEN** the package-private administration interface and Registry implementation are inaccessible
|
||||
- **THEN** the package-private administration interface and Registry implementation are inaccessible, and the public service exposes no path to its active manager
|
||||
|
||||
#### Scenario: Canonical process API is inspected
|
||||
- **WHEN** modern source packages are searched after migration
|
||||
- **THEN** exactly one canonical `com.r35157.nenjim.process.NenjimProcess` contract exists and no `NenjimApplication` type remains
|
||||
|
||||
### Requirement: Deferred runtime features remain absent
|
||||
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle.
|
||||
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle. The Hub SHALL not add configurable autorun, process-manager integration, stop, restart, upgrade, health monitoring, recovery, or context-bound dependency access.
|
||||
|
||||
#### Scenario: Consumer inspects the first Registry API
|
||||
- **WHEN** a consumer examines the public Registry and manager contracts
|
||||
- **THEN** only the agreed read-only lookup and manager start operations are available and no deferred feature is exposed
|
||||
- **WHEN** a consumer examines the public Registry, Registry-manager, process, and Hub contracts
|
||||
- **THEN** only read-only Registry lookup, passive registration, minimal process startup, and Hub startup/snapshot operations are available and no deferred feature is exposed
|
||||
|
||||
### Requirement: Components and processes have minimal orthogonal contracts
|
||||
`NenjimComponent` SHALL remain a pure marker with no component-ID or lifecycle property. The one canonical `com.r35157.nenjim.process.NenjimProcess` SHALL extend `NenjimComponent` and SHALL expose only `start() throws Exception` as its common lifecycle operation. Service and application SHALL remain independent domain or orchestration roles: an implementation in either role SHALL additionally implement `NenjimProcess` only when it has a Hub-managed active startup lifecycle. Passive components SHALL not implement `NenjimProcess`, and no `NenjimApplication` compatibility type SHALL remain.
|
||||
|
||||
#### Scenario: One object has more than one registration identity
|
||||
- **WHEN** the same component object is registered under two different unused component IDs
|
||||
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
|
||||
|
||||
#### Scenario: A service implementation is startable
|
||||
- **WHEN** a service implementation has a no-argument active startup lifecycle managed by the Hub
|
||||
- **THEN** the implementation is discoverable as `NenjimProcess` without adding lifecycle operations solely to its domain service interface
|
||||
|
||||
#### Scenario: Application role and process lifecycle are independent
|
||||
- **WHEN** a component performs a user-facing or orchestration application role
|
||||
- **THEN** that role alone neither makes it a process nor changes its domain interfaces
|
||||
|
||||
#### Scenario: Common stop is unavailable
|
||||
- **WHEN** a caller uses `NenjimProcess`
|
||||
- **THEN** the contract exposes no stop, restart, health, context, or dependency-lookup operation
|
||||
|
||||
### Requirement: Registry construction preserves the administrative boundary
|
||||
The reference Registry construction boundary SHALL create one Registry object exposed through both `NenjimRegistryService` and `NenjimProcess`, plus one distinct `NenjimRegistryServiceManager` bound to that Registry. The service and process views SHALL preserve object identity. The construction helper SHALL own no lifecycle, SHALL be neither a component nor a process, SHALL not be Registry-discoverable, and SHALL expose neither the package-private administration interface nor a way to derive the active Registry's manager from an ordinary service reference.
|
||||
|
||||
#### Scenario: Registry views preserve identity
|
||||
- **WHEN** the Hub obtains a newly constructed Registry assembly
|
||||
- **THEN** its service and process views reference the identical Registry object while its manager is a different object
|
||||
|
||||
#### Scenario: Ordinary service reference remains read-only
|
||||
- **WHEN** an ordinary consumer receives the public Registry service view
|
||||
- **THEN** the consumer cannot name or derive the package-private administration view or obtain the manager bound to that active Registry
|
||||
|
||||
### Requirement: Registry manager is a passive registration handle
|
||||
`NenjimRegistryServiceManager` SHALL expose exactly registration by component ID and component. It SHALL be neither a Nenjim component nor a Nenjim process, SHALL remain privately retained by the Hub, and SHALL delegate registration to the Registry's internal administration view while preserving duplicate-ID, interface-indexing, and post-start registration behavior. It SHALL not construct the business graph, own lifecycle, start or stop any process, expose the Registry service, or be registered under `nenjim.registry.service-manager` or any other component ID.
|
||||
|
||||
#### Scenario: Hub registers a component
|
||||
- **WHEN** the Hub passes an unused component ID and a non-null component to the manager
|
||||
- **THEN** the component becomes available through the Registry's existing typed interface index without being activated
|
||||
|
||||
#### Scenario: Registry manager is not discoverable
|
||||
- **WHEN** Registry registrations are inspected after Hub composition
|
||||
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
||||
|
||||
### Requirement: NenjimHub owns composition and runtime startup
|
||||
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the complete hardcoded component set from issue #76 in dependency order, resolve only the explicitly enabled IDs through `NenjimProcess`, start them in dependency-safe order, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup.
|
||||
|
||||
#### Scenario: Registry starts before graph construction
|
||||
- **WHEN** the Hub performs its first startup attempt
|
||||
- **THEN** it starts the Registry, registers it as `nenjim.registry.service`, and records that ID before constructing components whose constructors may query the Registry
|
||||
|
||||
#### Scenario: Registry is process-discoverable
|
||||
- **WHEN** the Registry registration has completed
|
||||
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||
|
||||
#### Scenario: Required hardcoded bindings are registered
|
||||
- **WHEN** Hub composition completes
|
||||
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding the former Registry-manager ID
|
||||
|
||||
#### Scenario: Explicit startup subset preserves order
|
||||
- **WHEN** the Hub starts processes after the Registry
|
||||
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
|
||||
|
||||
#### Scenario: Deliberately inactive processes remain inactive
|
||||
- **WHEN** Hub startup reaches its online point
|
||||
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
|
||||
|
||||
#### Scenario: Hub start is not retried
|
||||
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||
|
||||
#### Scenario: Startup failure is fail-fast
|
||||
- **WHEN** a process startup throws
|
||||
- **THEN** the exception propagates, no later process starts, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||
|
||||
### Requirement: Started-process snapshots are ordered immutable and truthful
|
||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||
|
||||
#### Scenario: Snapshot before process startup
|
||||
- **WHEN** the snapshot is requested before any process has started successfully
|
||||
- **THEN** it is an empty immutable list
|
||||
|
||||
#### Scenario: Successful process is recorded
|
||||
- **WHEN** a process's `start()` call returns successfully
|
||||
- **THEN** its ID appears after all previously successful process IDs in every later snapshot
|
||||
|
||||
#### Scenario: Failed process is not recorded
|
||||
- **WHEN** a process's `start()` call throws
|
||||
- **THEN** its ID is absent while all earlier successful process IDs remain visible
|
||||
|
||||
#### Scenario: Snapshot is a point-in-time value
|
||||
- **WHEN** the Hub starts another process after a caller obtained a snapshot
|
||||
- **THEN** the earlier snapshot is unchanged and cannot be mutated by its caller
|
||||
|
||||
Reference in New Issue
Block a user