78: Make JournalService a Hub-managed Nenjim process and integrate it into startup
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 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. The Hub SHALL then resolve only the explicitly enabled business 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, and both the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
||||
|
||||
#### Scenario: Infrastructure starts before business composition
|
||||
- **WHEN** the Hub performs its first startup attempt
|
||||
- **THEN** it starts, registers and records Registry first, then registers, starts and records Journal before constructing components in the business graph
|
||||
|
||||
#### 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 Journal registration has completed
|
||||
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||
|
||||
#### Scenario: Required hardcoded bindings are registered
|
||||
- **WHEN** Hub composition completes
|
||||
- **THEN** Journal 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
|
||||
|
||||
#### Scenario: Explicit startup subset preserves order
|
||||
- **WHEN** Hub startup succeeds through the business-process phase
|
||||
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, and `evelyn.mission-control.default`
|
||||
|
||||
#### 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: Journal failure is fail-fast
|
||||
- **WHEN** Journal 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
|
||||
|
||||
#### 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: Later startup failure is fail-fast
|
||||
- **WHEN** a business 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
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Journal construction preserves process identity and administration boundaries
|
||||
The reference Journal construction boundary SHALL create one Journal object configured with a normalized absolute data root and exposed through both `JournalService` and `NenjimProcess`, plus one distinct `JournalServiceManager` bound to that object through a package-private administration view. 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 registered, and SHALL expose neither parser, ingestion, chain state, filesystem administration nor a way to derive the active Journal's manager from an ordinary service reference.
|
||||
|
||||
#### Scenario: Journal views preserve identity
|
||||
- **WHEN** the Hub obtains a newly constructed Journal assembly
|
||||
- **THEN** its service and process views reference the identical Journal object while its manager is a different object
|
||||
|
||||
#### Scenario: Ordinary service reference remains read-only
|
||||
- **WHEN** an ordinary consumer receives the public Journal service view
|
||||
- **THEN** the consumer cannot name or derive the package-private administration view or obtain the manager bound to that active Journal
|
||||
|
||||
### Requirement: Journal process startup is single-use and publishes atomically
|
||||
The reference Journal service object SHALL implement `NenjimProcess`. Its first `start()` call SHALL be the only accepted attempt, SHALL load only its construction-configured root, and SHALL make query state available only after the complete initial filesystem load succeeds. A later start SHALL fail with `IllegalStateException`, including after a failed first attempt. Queries before successful startup or after failed startup SHALL fail with `IllegalStateException`, and no failed initial load SHALL expose a partial Journal worldview.
|
||||
|
||||
#### Scenario: Successful initial process start
|
||||
- **WHEN** the configured root and every runtime Journal entry are valid
|
||||
- **THEN** the process returns only after the complete chronological load is queryable
|
||||
|
||||
#### Scenario: Example-only root starts empty
|
||||
- **WHEN** the configured root exists and contains only valid artifact directories with `.journal.example` files
|
||||
- **THEN** process startup succeeds and the service exposes an empty immutable coordinate set
|
||||
|
||||
#### Scenario: Query before successful startup
|
||||
- **WHEN** any Journal query is called before startup succeeds or after startup fails
|
||||
- **THEN** it fails with `IllegalStateException` and exposes no candidate state
|
||||
|
||||
#### Scenario: Failed startup is not retried
|
||||
- **WHEN** the first Journal process start fails and `start()` is called again
|
||||
- **THEN** the second call fails with `IllegalStateException` without another loading attempt
|
||||
|
||||
#### Scenario: Failed initial load publishes nothing
|
||||
- **WHEN** an earlier runtime entry is valid but any later entry fails root, layout, parsing, placement or chain validation
|
||||
- **THEN** the original meaningful failure propagates and no accepted candidate revision becomes queryable
|
||||
|
||||
### Requirement: Passive Journal service manager
|
||||
`JournalServiceManager` SHALL expose exactly the normalized configured `journalRoot()` and explicit `refresh()` administration operations. It SHALL be neither a Nenjim component nor a Nenjim process, SHALL be retained privately by the Hub, SHALL bind to exactly one Journal object through non-public administration, and SHALL expose no start, stop, service accessor, query, parser, ingestion or chain-state operation. Refresh before successful Journal process startup SHALL fail with `IllegalStateException`.
|
||||
|
||||
#### Scenario: Manager contract is passive
|
||||
- **WHEN** the public manager interface and reference manager are inspected
|
||||
- **THEN** only root reporting and refresh are available and the manager has no lifecycle or component identity
|
||||
|
||||
#### Scenario: Refresh follows process startup
|
||||
- **WHEN** refresh is requested before its Journal process has started successfully
|
||||
- **THEN** the manager rejects it with `IllegalStateException`
|
||||
|
||||
#### Scenario: Manager delegates after startup
|
||||
- **WHEN** refresh is requested after successful Journal startup
|
||||
- **THEN** the manager delegates through the non-public admin view to the same active Journal object
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Filesystem-independent read-only Journal service
|
||||
`JournalService` SHALL extend `NenjimComponent` as the ordinary registrable, application-facing, filesystem-independent, read-only API. It SHALL list known artifact coordinates and chronological revisions; obtain latest, exact, and newest-at-or-before revisions; obtain an exact artifact release from a selected revision; return immutable results without `null`; and expose no lifecycle, path, refresh, parser, configuration, administration or mutation operation. Its queries SHALL be available only after the Journal process has completed successful startup.
|
||||
|
||||
#### Scenario: Chronological and latest queries
|
||||
- **WHEN** an artifact has several accepted revisions after successful startup
|
||||
- **THEN** the service lists all complete snapshots in ascending Journal-version order and returns the chain head as latest
|
||||
|
||||
#### Scenario: Exact and as-of historical queries
|
||||
- **WHEN** a caller requests an exact revision or the newest revision at or before a Journal version
|
||||
- **THEN** the service returns the complete worldview from that chain position rather than filtering a newer snapshot by release publication time
|
||||
|
||||
#### Scenario: Release query from selected worldview
|
||||
- **WHEN** a caller requests an exact artifact version from a selected revision
|
||||
- **THEN** the release, including license, content, dependencies, and policy context in that snapshot, is returned only if that revision knows it
|
||||
|
||||
#### Scenario: Missing query result
|
||||
- **WHEN** a coordinate, revision, as-of revision, or artifact release is unknown after successful startup
|
||||
- **THEN** the service returns an empty optional or immutable empty collection rather than `null`
|
||||
|
||||
#### Scenario: Read-only component boundary
|
||||
- **WHEN** an ordinary consumer receives `JournalService`
|
||||
- **THEN** the interface is registrable as a component but provides no start, stop, filesystem root, refresh, parsing, update, ingestion, administration or other mutation operation
|
||||
|
||||
### Requirement: Filesystem-backed lifecycle loading
|
||||
The reference Journal service process SHALL own a configured existing readable root and SHALL use non-public filesystem loading to discover runtime files only at `<root>/<artifact-coordinate>/<journal-version>.journal`, ignore `.journal.example`, validate directory and filename placement against parsed content, sort each artifact's files chronologically, read complete UTF-8 text, and supply it to the existing internal ingestion operation. Initial loading SHALL publish only after the complete root succeeds. Later refresh SHALL target the active service through its non-public administration view. Neither path SHALL create, overwrite, delete, publish, download, synchronize or watch files.
|
||||
|
||||
#### Scenario: Valid filesystem load
|
||||
- **WHEN** an artifact directory contains chronologically named valid genesis and successor `.journal` files whose content identity matches their placement
|
||||
- **THEN** initial process startup or a later explicit refresh ingests both complete UTF-8 texts in chronological order and exposes their chain through the service
|
||||
|
||||
#### Scenario: Example files are ignored
|
||||
- **WHEN** a directory contains `<journal-version>.journal.example`
|
||||
- **THEN** loading does not ingest it as a runtime Journal
|
||||
|
||||
#### Scenario: Invalid directory placement
|
||||
- **WHEN** a runtime Journal's metadata-derived coordinate differs from its parent directory name
|
||||
- **THEN** loading fails clearly
|
||||
|
||||
#### Scenario: Invalid filename placement
|
||||
- **WHEN** a runtime filename does not use the strict timestamp syntax or differs from the parsed `JOURNAL_VERSION`
|
||||
- **THEN** loading fails clearly
|
||||
|
||||
#### Scenario: Invalid root
|
||||
- **WHEN** the configured Journal root is missing, not a directory, or unreadable
|
||||
- **THEN** startup or refresh fails clearly without creating it
|
||||
|
||||
#### Scenario: Unexpected filesystem entry
|
||||
- **WHEN** the configured root contains a runtime entry outside the artifact-directory/revision-file structure
|
||||
- **THEN** loading fails rather than silently treating it as a Journal
|
||||
|
||||
### Requirement: Explicit idempotent refresh
|
||||
The passive service manager SHALL support explicit refresh only after successful Journal process startup. Repeating refresh for unchanged files SHALL be idempotent, and a later refresh SHALL discover valid newly added successors while preserving existing historical revisions and the existing per-ingestion atomicity.
|
||||
|
||||
#### Scenario: Unchanged refresh
|
||||
- **WHEN** refresh runs twice after startup without filesystem changes
|
||||
- **THEN** the second run leaves all chains unchanged and succeeds through ingestion idempotency
|
||||
|
||||
#### Scenario: New revision discovered
|
||||
- **WHEN** a valid successor file is added after successful startup or an earlier refresh
|
||||
- **THEN** the next refresh ingests it and the read-only service exposes it as the new head
|
||||
|
||||
#### Scenario: Refresh is not a service operation
|
||||
- **WHEN** ordinary consumers inspect `JournalService` and `NenjimProcess`
|
||||
- **THEN** neither interface exposes refresh
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Journal service lifecycle manager
|
||||
**Reason**: Lifecycle ownership belongs to the Journal service object as a Hub-managed `NenjimProcess`; the manager is only a passive administrative capability.
|
||||
|
||||
**Migration**: The Hub starts the Journal through its process view and retains a distinct manager exposing only `journalRoot()` and `refresh()`.
|
||||
Reference in New Issue
Block a user