Files
com_r35157_nenjim-hubd-impl…/openspec/specs/nenjim-component-registry/spec.md
T

22 KiB

nenjim-component-registry Specification

Purpose

Provide one typed, read-only catalogue of constructed Nenjim components and a lifecycle manager that owns the current hardcoded composition without introducing Context or dynamic-loading semantics.

Requirements

Requirement: Component IDs are canonical validated values

NenjimComponentId SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.

Scenario: Surrounding whitespace is canonicalized

  • WHEN an ID is constructed from nenjim.registry.service
  • THEN its stored value is nenjim.registry.service

Scenario: Valid component IDs are accepted

  • WHEN IDs such as nenjim.registry.service, assetaz.price-source.raydium-pool, or evelyn.status.prod are constructed
  • THEN construction succeeds

Scenario: Invalid component IDs are rejected

  • WHEN an ID is null or contains uppercase letters, underscores, internal whitespace, an empty segment, a leading or trailing dot, or a malformed hyphen
  • THEN construction fails with the specified null or argument exception category and identifies the invalid canonicalized value without exposing secrets

Requirement: Registry consumers receive only typed read-only lookup

NenjimRegistryService SHALL be a Nenjim component and SHALL expose exactly an ordered-ID lookup by component interface and a nullable typed lookup by component ID and expected component interface. Both operations SHALL reject null arguments. A query type SHALL be an interface extending NenjimComponent; concrete classes and ordinary interfaces SHALL be rejected.

Scenario: List matching component IDs

  • WHEN a caller requests the IDs for a valid component interface
  • THEN the Registry returns all registrations indexed under that interface in registration order as an immutable snapshot that is not changed by later registrations

Scenario: No component implements an interface

  • WHEN a valid component interface has no indexed registration
  • THEN the Registry returns an empty immutable list rather than null

Scenario: Retrieve a matching component

  • WHEN an existing component ID is requested through an interface implemented by its object
  • THEN the Registry returns that object cast to the requested interface

Scenario: Component ID is missing

  • WHEN an unused component ID is requested through a valid component interface
  • THEN the Registry returns null

Scenario: Existing component has the wrong type

  • WHEN an existing component ID is requested through a component interface its object does not implement
  • THEN lookup fails with IllegalArgumentException identifying the ID, requested interface name, and actual implementation class name

Scenario: Query token is not a component interface

  • WHEN a caller supplies a concrete class, an ordinary interface, or null as a query token
  • THEN the Registry rejects it with the specified argument or null exception category

Scenario: Consumer cannot administer registrations

  • WHEN ordinary code receives NenjimRegistryService
  • THEN that public view exposes no registration, removal, loading, resolver, class-loading, stop, permission, event, or persistence operation

Requirement: Internal registration maintains an interface index

The internal Registry administration view SHALL remain package-private, SHALL not be a Nenjim component, and SHALL expose registration but no removal. Each registration SHALL atomically add a unique ID to a primary ID-to-object catalogue and to a secondary ordered index under every implemented interface in the recursive hierarchy that extends NenjimComponent. It SHALL not index concrete classes, ordinary interfaces, or the administration interface. Reads and registrations SHALL remain internally consistent when registrations occur after startup.

Scenario: Duplicate ID is rejected

  • WHEN a component is registered under an ID already present in the Registry
  • THEN registration fails with IllegalArgumentException identifying the duplicated ID and both the existing and attempted implementation class names without changing either index

Scenario: Null registration input is rejected

  • WHEN a registration supplies a null ID or component
  • THEN registration fails with NullPointerException without changing either index

Scenario: Same instance receives aliases

  • WHEN the same object is registered under multiple different unused IDs
  • THEN every registration succeeds and each ID resolves to the identical object reference

Scenario: Inherited component interfaces are indexed

  • WHEN a registered object implements a component interface that extends another component interface
  • THEN the registration ID appears under both interfaces, including inherited interfaces not directly declared by the implementation class

Scenario: One registration exposes multiple views

  • WHEN one object implements multiple component interfaces and is registered once
  • THEN its one ID appears under every implemented component interface and typed lookups preserve reference identity

Scenario: Registration occurs after startup

  • WHEN internal administration registers another component after the Registry has started
  • THEN subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged

Requirement: Registry startup is single-use and gates queries

The Registry process's first start() call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with IllegalStateException, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.

Scenario: Query before startup

  • WHEN either lookup operation is called before successful Registry startup
  • THEN it fails with IllegalStateException

Scenario: Registry starts once

  • WHEN the Registry is started successfully and start is requested again
  • THEN the second request fails with IllegalStateException

Scenario: Failed start is not retried

  • WHEN the first Registry start attempt fails
  • THEN every later start request fails with IllegalStateException

Requirement: Applications own explicit dependency selection

A non-bootstrap application or service that owns a component choice SHALL receive the public Registry view through constructor injection, express the choice as one or more explicit component IDs, and use typed lookup. A required missing or wrong-type binding SHALL fail clearly rather than selecting the first component returned for an interface. The initial Ticker composition SHALL explicitly select only assetaz.price-source.raydium-pool.eve-usdt; assetaz.price-source.hardcoded SHALL remain registered but inactive.

Scenario: Ticker resolves its configured source

  • WHEN the Ticker is constructed with the public Registry and the Raydium EVE/USDT source ID
  • THEN it resolves that exact PriceSource and does not activate the hardcoded source

Scenario: Required configured dependency is missing

  • WHEN an application resolves a required hardcoded component ID that is absent
  • THEN construction or startup fails with a diagnostic identifying the required ID and interface without exposing component secrets

Scenario: Internal administration is not injected

  • WHEN an ordinary application receives its Registry dependency
  • THEN it receives only NenjimRegistryService and cannot self-register

Requirement: Main is a thin outer bootstrap

com.r35157.nenjim.hubd.Main SHALL be a final non-component class. 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: Registry terminology and package boundaries are consistent

Public component contracts SHALL live in com.r35157.nenjim.component; the canonical process contract SHALL live in com.r35157.nenjim.process; the global Hub contract SHALL live in com.r35157.nenjim.hubd; public Registry service contracts SHALL live in com.r35157.nenjim.service.registry; and the component ID SHALL live in its .valuetypes package. Registry storage and its package-private administration view SHALL live in .impl.ref, where an implementation-only construction boundary MAY also live without exposing administration. Directly affected Ticker price-source API and implementation packages SHALL continue to use .pricesource. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.

Scenario: Ticker source packages are inspected

  • WHEN source packages and imports are searched after migration
  • THEN no directly affected Ticker price-source package uses the plugins segment

Scenario: Internal Registry administration is inspected

  • WHEN an ordinary consumer compiles against the public Registry packages
  • THEN the package-private administration interface and Registry implementation are inaccessible, and the public service exposes no path to its active manager

Scenario: Canonical process API is inspected

  • WHEN modern source packages are searched after migration
  • THEN exactly one canonical com.r35157.nenjim.process.NenjimProcess contract exists and no NenjimApplication type remains

Requirement: Deferred runtime features remain absent

The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, 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: 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 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

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.

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: JavaFX runtime uses the common recording rule

  • WHEN JavaFX runtime service start() returns successfully
  • THEN the Hub records nenjim.javafx-runtime.service through the same post-start rule used for every other process

Scenario: Process Manager uses the common recording rule

  • WHEN Process Manager start() returns successfully
  • THEN the Hub records nenjim.process-manager.default through the same post-start rule used for every other process

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