76: Introduce the Nenjim component registry and move runtime composition into its manager

This commit is contained in:
2026-08-30 14:28:49 +02:00
parent b230e15ece
commit a252c256c4
55 changed files with 1451 additions and 480 deletions
@@ -67,7 +67,7 @@ The Currency Identity Service SHALL return all configured external references fo
- **THEN** the result contains its configured Solana mint reference
### Requirement: Immutable conflict-free hardcoded catalogue
The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. NenjimHub SHALL create exactly one hardcoded service instance for its autorun context and SHALL pass that same instance to every autorun component that requires currency identities.
The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. The Nenjim Registry service manager SHALL create exactly one hardcoded service instance for the current hardcoded component graph, register it as `assetaz.currency-identity.hardcoded`, and SHALL pass that same instance to every component in that graph that requires currency identities.
#### Scenario: Conflicting external mapping
- **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs
@@ -77,9 +77,9 @@ The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ curr
- **WHEN** initialization provides conflicting current values for the same UUID
- **THEN** initialization fails with a clear conflict exception
#### Scenario: Compose autorun components
- **WHEN** NenjimHub constructs its autorun component graph
- **THEN** it creates one hardcoded Currency Identity Service and injects that same instance into every component that requires it
#### Scenario: Compose hardcoded components
- **WHEN** the Registry service manager constructs the current hardcoded component graph
- **THEN** it creates and registers one hardcoded Currency Identity Service and injects that same instance into every component that requires it
### Requirement: Trading pairs use service-owned identities
Production code SHALL construct trading pairs from currency values obtained from the current Currency Identity Service and SHALL NOT maintain a parallel static registry of canonical currency or trading-pair instances.
@@ -2,7 +2,7 @@
## Purpose
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource plugin contract.
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource component contract.
## Requirements
+26 -22
View File
@@ -7,26 +7,30 @@ Provide AssetAZ callers with typed latest prices backed by explicitly enabled, d
## Requirements
### Requirement: Price sources are discovered and lifecycle-managed
Nenjim SHALL be able to construct each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL obtain `PriceSource` plugins from its Nenjim context, while ordinary ticker clients SHALL NOT receive a public operation for adding or managing sources. The ticker SHALL identify each obtained source by its `TradingPair` and stable source name, reject duplicate identities clearly, start each active source by passing itself as `PriceSink`, and stop every source it started. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
Nenjim SHALL be able to construct and register each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL receive the public Nenjim Registry through constructor injection and resolve its explicitly configured `PriceSource` component IDs, while ordinary ticker clients SHALL NOT receive a public operation for adding or managing sources. The ticker SHALL fail clearly when a configured ID is missing or names a component that does not implement `PriceSource`; it SHALL NOT silently select the first source listed for the interface. The ticker SHALL identify each resolved source by its `TradingPair` and stable source name, reject duplicate identities clearly, start each active source by passing itself as `PriceSink`, and stop every source it started. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
#### Scenario: Sources are constructed independently
- **WHEN** Nenjim constructs a price source before constructing or starting the ticker
- **WHEN** the Registry manager constructs and registers a price source before constructing or starting the ticker
- **THEN** source construction requires neither a `PriceSink` nor a ticker reference and leaves the source unstarted
#### Scenario: Ticker obtains source plugins
- **WHEN** the ticker initializes its source set from its Nenjim context
- **THEN** it looks for implementations of `PriceSource`, not implementations of `PriceSink`, and manages the obtained sources internally
#### Scenario: Ticker resolves configured source components
- **WHEN** the ticker initializes its source set from explicit component IDs and an injected public Registry
- **THEN** it resolves each ID as `PriceSource`, not `PriceSink`, and manages only the resolved configured sources internally
#### Scenario: Ordinary clients cannot manage plugins
#### Scenario: Missing configured source fails clearly
- **WHEN** the ticker is configured with an absent component ID or an ID whose object is not a `PriceSource`
- **THEN** construction fails with a diagnostic identifying the ID and required interface
#### Scenario: Ordinary clients cannot manage sources
- **WHEN** a caller uses the public `TickerService` contract
- **THEN** the contract exposes ticker lifecycle and latest-price queries but no source-registration operation
#### Scenario: Distinct context sources are accepted
- **WHEN** the ticker obtains two sources with different source names or trading pairs from its context
#### Scenario: Distinct configured sources are accepted
- **WHEN** the ticker resolves two configured sources with different source names or trading pairs
- **THEN** it accepts both and manages each source independently
#### Scenario: Duplicate persistent identity is rejected
- **WHEN** the ticker obtains sources with the same trading pair and source name
- **WHEN** the ticker resolves sources with the same trading pair and source name
- **THEN** initialization fails with an error identifying the duplicate source history
#### Scenario: Unsafe source name is rejected
@@ -34,7 +38,7 @@ Nenjim SHALL be able to construct each price source independently in an initiali
- **THEN** ticker initialization or startup fails clearly without rewriting the source name or creating filesystem content
#### Scenario: Active source lifecycle is managed
- **WHEN** the ticker starts and later stops with an active context-provided source
- **WHEN** the ticker starts and later stops with an active Registry-resolved source
- **THEN** it calls `start` with itself as `PriceSink` and subsequently stops the source without leaving source-owned resources running
#### Scenario: Repeated source start preserves existing sink
@@ -42,7 +46,7 @@ Nenjim SHALL be able to construct each price source independently in an initiali
- **THEN** it rejects the call before replacing the sink from the successful start
### Requirement: Ticker exposes typed latest prices
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active context-provided sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no obtained sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active Registry-resolved sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no resolved sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
#### Scenario: Latest supported price is available
- **WHEN** a caller requests a trading pair after at least one observation has been loaded or persisted by an active source
@@ -52,8 +56,8 @@ The ticker SHALL expose the latest successfully persisted `PriceObservation` for
- **WHEN** multiple active sources for one trading pair have successfully persisted observations
- **THEN** the ticker returns the observation with the greatest `observedAt` across those sources
#### Scenario: Pair has no obtained or active source
- **WHEN** a caller requests a pair with no context-provided source or whose obtained sources are all inactive
#### Scenario: Pair has no resolved or active source
- **WHEN** a caller requests a pair with no configured Registry-resolved source or whose resolved sources are all inactive
- **THEN** the ticker throws an exception that clearly identifies the unavailable trading pair
#### Scenario: Active pair has no persisted observation
@@ -61,22 +65,22 @@ The ticker SHALL expose the latest successfully persisted `PriceObservation` for
- **THEN** the ticker throws an exception that clearly states that no persisted price is available for that trading pair
### Requirement: Price history explicitly activates each source
For each context-provided source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the obtained source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
For each configured Registry-resolved source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the resolved source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
#### Scenario: Existing source history activates and restores identity
- **WHEN** a context-provided source has an existing valid history with permitted whitespace or comments
- **WHEN** a Registry-resolved source has an existing valid history with permitted whitespace or comments
- **THEN** that source is active and its observations are restored with its captured trading pair and source name
#### Scenario: Empty source history activates without a price
- **WHEN** a context-provided source has an existing empty or comment-only history file
- **WHEN** a Registry-resolved source has an existing empty or comment-only history file
- **THEN** that source is active without an initial observation
#### Scenario: Missing source history leaves only that source inactive
- **WHEN** one obtained source history is missing while another source for the same pair has an existing history
- **WHEN** one resolved source history is missing while another source for the same pair has an existing history
- **THEN** the missing source is not started, the existing source remains active, and no missing path is created
#### Scenario: Missing history warning identifies the source
- **WHEN** a context-provided source's expected history file is absent
- **WHEN** a Registry-resolved source's expected history file is absent
- **THEN** the ticker logs a warning containing its trading pair, source name, and complete expected UUID-based path
#### Scenario: Malformed source history is rejected clearly
@@ -88,7 +92,7 @@ For each context-provided source, the ticker SHALL derive an independent history
- **THEN** the ticker does not read, move, copy, or create a replacement for that legacy file
### Requirement: History uses the human-editable observation format
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the context-provided source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the Registry-resolved source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
#### Scenario: Observation is written in the required format
- **WHEN** any source observation at `2026-08-05T13:15:42.783Z` with price `14.85` is persisted
@@ -103,14 +107,14 @@ Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, w
- **THEN** those existing lines remain unchanged and the appended observation contains neither a comment nor extra annotation
### Requirement: New observations are durable before publication
Only an active source obtained and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
Only an active source resolved and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
#### Scenario: Active obtained source observation is persisted
#### Scenario: Active resolved source observation is persisted
- **WHEN** an active source started by the ticker announces a valid price and timestamp
- **THEN** the ticker appends and forces the observation to that source's history before considering it for latest
#### Scenario: Inactive or unknown source callback is rejected
- **WHEN** an inactive source or a source not obtained by the ticker announces an observation
- **WHEN** an inactive source or a source not resolved by the ticker announces an observation
- **THEN** the ticker rejects the callback without persisting or publishing it
#### Scenario: Future-dated observation remains latest
@@ -0,0 +1,181 @@
# nenjim-component-registry Specification
## Purpose
Provide one typed, read-only catalogue of constructed Nenjim components and a lifecycle manager that owns the current hardcoded composition without introducing Context or dynamic-loading semantics.
## Requirements
### Requirement: Components and applications have minimal common contracts
`NenjimComponent` SHALL be a pure marker with no component-ID property. `NenjimApplication` SHALL extend `NenjimComponent` and expose only `start() throws Exception` as its common lifecycle operation. Domain service interfaces SHALL remain focused on their domain APIs, while a concrete service implementation with a no-argument active start lifecycle SHALL additionally implement `NenjimApplication`.
#### Scenario: One object has more than one registration identity
- **WHEN** the same component object is registered under two different unused component IDs
- **THEN** both IDs identify the same object because IDs are Registry metadata rather than object properties
#### Scenario: A service implementation is startable
- **WHEN** a service implementation has a no-argument active start lifecycle
- **THEN** the implementation is discoverable as `NenjimApplication` without adding lifecycle operations solely to its domain service interface
#### Scenario: Common stop is unavailable
- **WHEN** a caller uses `NenjimApplication`
- **THEN** the contract exposes no common stop operation
### Requirement: Component IDs are canonical validated values
`NenjimComponentId` SHALL reject null, strip surrounding Unicode whitespace before storage and validation, and accept only lower-case ASCII dot-separated segments whose first character is a letter and whose remaining subparts contain lower-case letters or digits separated by single internal hyphens. Standard record value equality and hashing SHALL define ID equality.
#### Scenario: Surrounding whitespace is canonicalized
- **WHEN** an ID is constructed from ` nenjim.registry.service `
- **THEN** its stored value is `nenjim.registry.service`
#### Scenario: Valid component IDs are accepted
- **WHEN** IDs such as `nenjim.registry.service`, `assetaz.price-source.raydium-pool`, or `evelyn.service.prod` are constructed
- **THEN** construction succeeds
#### Scenario: Invalid component IDs are rejected
- **WHEN** an ID is null or contains uppercase letters, underscores, internal whitespace, an empty segment, a leading or trailing dot, or a malformed hyphen
- **THEN** construction fails with the specified null or argument exception category and identifies the invalid canonicalized value without exposing secrets
### Requirement: Registry consumers receive only typed read-only lookup
`NenjimRegistryService` SHALL be a Nenjim component and SHALL expose exactly an ordered-ID lookup by component interface and a nullable typed lookup by component ID and expected component interface. Both operations SHALL reject null arguments. A query type SHALL be an interface extending `NenjimComponent`; concrete classes and ordinary interfaces SHALL be rejected.
#### Scenario: List matching component IDs
- **WHEN** a caller requests the IDs for a valid component interface
- **THEN** the Registry returns all registrations indexed under that interface in registration order as an immutable snapshot that is not changed by later registrations
#### Scenario: No component implements an interface
- **WHEN** a valid component interface has no indexed registration
- **THEN** the Registry returns an empty immutable list rather than null
#### Scenario: Retrieve a matching component
- **WHEN** an existing component ID is requested through an interface implemented by its object
- **THEN** the Registry returns that object cast to the requested interface
#### Scenario: Component ID is missing
- **WHEN** an unused component ID is requested through a valid component interface
- **THEN** the Registry returns null
#### Scenario: Existing component has the wrong type
- **WHEN** an existing component ID is requested through a component interface its object does not implement
- **THEN** lookup fails with `IllegalArgumentException` identifying the ID, requested interface name, and actual implementation class name
#### Scenario: Query token is not a component interface
- **WHEN** a caller supplies a concrete class, an ordinary interface, or null as a query token
- **THEN** the Registry rejects it with the specified argument or null exception category
#### Scenario: Consumer cannot administer registrations
- **WHEN** ordinary code receives `NenjimRegistryService`
- **THEN** that public view exposes no registration, removal, loading, resolver, class-loading, stop, permission, event, or persistence operation
### Requirement: Internal registration maintains an interface index
The internal Registry administration view SHALL remain package-private, SHALL not be a Nenjim component, and SHALL expose registration but no removal. Each registration SHALL atomically add a unique ID to a primary ID-to-object catalogue and to a secondary ordered index under every implemented interface in the recursive hierarchy that extends `NenjimComponent`. It SHALL not index concrete classes, ordinary interfaces, or the administration interface. Reads and registrations SHALL remain internally consistent when registrations occur after startup.
#### Scenario: Duplicate ID is rejected
- **WHEN** a component is registered under an ID already present in the Registry
- **THEN** registration fails with `IllegalArgumentException` identifying the duplicated ID and both the existing and attempted implementation class names without changing either index
#### Scenario: Null registration input is rejected
- **WHEN** a registration supplies a null ID or component
- **THEN** registration fails with `NullPointerException` without changing either index
#### Scenario: Same instance receives aliases
- **WHEN** the same object is registered under multiple different unused IDs
- **THEN** every registration succeeds and each ID resolves to the identical object reference
#### Scenario: Inherited component interfaces are indexed
- **WHEN** a registered object implements a component interface that extends another component interface
- **THEN** the registration ID appears under both interfaces, including inherited interfaces not directly declared by the implementation class
#### Scenario: One registration exposes multiple views
- **WHEN** one object implements multiple component interfaces and is registered once
- **THEN** its one ID appears under every implemented component interface and typed lookups preserve reference identity
#### Scenario: Registration occurs after startup
- **WHEN** internal administration registers another component after the Registry has started
- **THEN** subsequent queries observe a consistent registration while previously returned ID snapshots remain unchanged
### Requirement: Registry startup is single-use and gates queries
The Registry application's first `start()` call SHALL be the only accepted attempt and SHALL enable queries only after successful startup. A later attempt SHALL fail with `IllegalStateException`, including when the first attempt failed. Registration SHALL remain allowed before and after startup, and starting the Registry SHALL not construct, register, start, or stop another component.
#### Scenario: Query before startup
- **WHEN** either lookup operation is called before successful Registry startup
- **THEN** it fails with `IllegalStateException`
#### Scenario: Registry starts once
- **WHEN** the Registry is started successfully and start is requested again
- **THEN** the second request fails with `IllegalStateException`
#### Scenario: Failed start is not retried
- **WHEN** the first Registry start attempt fails
- **THEN** every later start request fails with `IllegalStateException`
### Requirement: Registry manager owns hardcoded composition and startup
`NenjimRegistryServiceManager` SHALL be a Nenjim component whose only public operation is `start() throws Exception`. Its public reference implementation SHALL be a distinct `NenjimApplication` object and SHALL accept only one start attempt, including after failure. The first attempt SHALL construct and directly start one Registry object, register that object first as `nenjim.registry.service`, register the manager second as `nenjim.registry.service-manager`, construct and register the complete required hardcoded component set in dependency order, start only the explicit active application subset in dependency-safe order, announce the existing online point, and preserve the existing blocking wait.
#### Scenario: Registry and manager views are registered
- **WHEN** manager composition reaches completed registration
- **THEN** the Registry object is retrievable by one ID as both `NenjimRegistryService` and `NenjimApplication`, the manager is retrievable by one different ID as both `NenjimRegistryServiceManager` and `NenjimApplication`, and the Registry and manager are different instances
#### Scenario: Required hardcoded bindings are registered
- **WHEN** manager composition completes
- **THEN** all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components
#### Scenario: Explicit startup subset preserves order
- **WHEN** the manager starts applications
- **THEN** it starts `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default` in that order
#### Scenario: Deliberately inactive applications remain inactive
- **WHEN** manager startup completes its explicit start sequence
- **THEN** `jupiter-perps-alarm.default` and the currently commented Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo Client are not started
#### Scenario: Manager start is not retried
- **WHEN** any first manager start attempt has begun and start is requested again
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
#### Scenario: Registration does not activate components
- **WHEN** the manager registers a component
- **THEN** registration alone neither starts nor stops that component
### Requirement: Applications own explicit dependency selection
A non-bootstrap application or service that owns a component choice SHALL receive the public Registry view through constructor injection, express the choice as one or more explicit component IDs, and use typed lookup. A required missing or wrong-type binding SHALL fail clearly rather than selecting the first component returned for an interface. The initial Ticker composition SHALL explicitly select only `assetaz.price-source.raydium-pool.eve-usdt`; `assetaz.price-source.hardcoded` SHALL remain registered but inactive.
#### Scenario: Ticker resolves its configured source
- **WHEN** the Ticker is constructed with the public Registry and the Raydium EVE/USDT source ID
- **THEN** it resolves that exact `PriceSource` and does not activate the hardcoded source
#### Scenario: Required configured dependency is missing
- **WHEN** an application resolves a required hardcoded component ID that is absent
- **THEN** construction or startup fails with a diagnostic identifying the required ID and interface without exposing component secrets
#### Scenario: Internal administration is not injected
- **WHEN** an ordinary application receives its Registry dependency
- **THEN** it receives only `NenjimRegistryService` and cannot self-register
### Requirement: Main is a thin outer bootstrap
`com.r35157.nenjim.hubd.Main` SHALL be a final non-component class that does not implement `NenjimApplication`. Its `main(String[])` SHALL construct `NenjimRegistryServiceManagerImpl` through a variable typed as `NenjimApplication` and start it. The Gradle application entry point SHALL name this class. Construction, registration, composition, and lifecycle ownership SHALL remain in `NenjimRegistryServiceManagerImpl`; the bootstrap SHALL perform none of those responsibilities beyond constructing and starting the manager.
#### Scenario: Start through the outer bootstrap
- **WHEN** the configured Java entry point is invoked
- **THEN** `Main.main(...)` constructs one Registry manager as a Nenjim application and delegates startup to it
#### Scenario: No duplicate composition path remains
- **WHEN** the source tree is inspected after migration
- **THEN** the old Hub interface, `com.r35157.nenjim.hubd.impl.ref.Main`, and the `NenjimHubImpl` composition implementation are absent, while `com.r35157.nenjim.hubd.Main` contains no replacement composition path
### Requirement: Registry terminology and package boundaries are consistent
Public component contracts SHALL live in `com.r35157.nenjim.component`; public Registry service contracts SHALL live in `com.r35157.nenjim.service.registry`; the component ID SHALL live in its `.valuetypes` package; and internal administration and Registry storage SHALL live in `.impl.ref`. Directly affected Ticker price-source API and implementation packages SHALL use `.pricesource` rather than `.plugins.pricesource`. Public reference-type parameters, return values, and record components added or changed by this capability SHALL declare explicit nullability.
#### Scenario: Ticker source packages are inspected
- **WHEN** source packages and imports are searched after migration
- **THEN** no directly affected Ticker price-source package uses the `plugins` segment
#### Scenario: Internal Registry administration is inspected
- **WHEN** an ordinary consumer compiles against the public Registry packages
- **THEN** the package-private administration interface and Registry implementation are inaccessible
### Requirement: Deferred runtime features remain absent
The Registry SHALL represent one catalogue of already constructed components and SHALL not implement Contexts, artifact-version identity, resolution, classloading, automatic discovery, contributor APIs, public runtime loading or registration, removal, persistence, events, subscribers, user configuration, automatic selection, automatic start-all, ownership tracking, usage counts, permissions, scopes, filtered views, or a common stop lifecycle.
#### Scenario: Consumer inspects the first Registry API
- **WHEN** a consumer examines the public Registry and manager contracts
- **THEN** only the agreed read-only lookup and manager start operations are available and no deferred feature is exposed