124 lines
5.9 KiB
Markdown
124 lines
5.9 KiB
Markdown
## Context
|
|
|
|
Issue #75 implemented a strict filesystem-backed Journal and put construction,
|
|
initial loading, service access, refresh and stop on `JournalServiceManager`.
|
|
Issue #77 subsequently established a common runtime architecture: the Hub
|
|
constructs and starts active `NenjimProcess` objects, domain service interfaces
|
|
stay lifecycle-free, and service managers are separate passive administrative
|
|
handles retained by the Hub.
|
|
|
|
The Journal format, parser, exact-text digest, chain validation, immutable
|
|
queries and explicit refresh behavior are already accepted and must remain
|
|
unchanged. Only ownership, lifecycle placement and Hub integration change.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
|
|
- Preserve one read-only registrable `JournalService` domain interface.
|
|
- Make the same Journal object available as the canonical `NenjimProcess`.
|
|
- Publish no queryable initial state until the complete filesystem load
|
|
succeeds.
|
|
- Keep refresh available only through a distinct passive manager.
|
|
- Start and record Journal second, after Registry and before business
|
|
composition/startup.
|
|
- Keep runtime files read-only and `.journal.example` files ignored.
|
|
|
|
**Non-Goals:**
|
|
|
|
- Journal format, model, parser, chain or policy redesign.
|
|
- Background watching, polling or remote synchronization.
|
|
- Generic stop, restart, health, rollback or recovery.
|
|
- Context, resolution, classloading, artifact transfer, configurable autorun or
|
|
Process Manager integration.
|
|
- New general unit tests or production test hooks.
|
|
|
|
## Decisions
|
|
|
|
### One hidden service implementation provides service, process and admin views
|
|
|
|
`JournalServiceImpl` remains under `.impl.ref` and becomes package-private. It
|
|
implements public `JournalService`, public `NenjimProcess`, and a new
|
|
package-private `JournalServiceAdmin`. Keeping the concrete type and admin view
|
|
non-public prevents ordinary service consumers from naming and casting to the
|
|
filesystem administration contract.
|
|
|
|
`JournalServiceAssembly.create(Path)` constructs one service object, exposes it
|
|
through identity-preserving service and process views, and constructs one
|
|
distinct `JournalServiceManagerImpl` against its admin view. The assembly owns
|
|
no lifecycle and exposes no reverse path from an active service reference to
|
|
its manager.
|
|
|
|
### The service process owns a single startup attempt
|
|
|
|
The configured root is normalized once during service construction. An atomic
|
|
start-attempt guard is consumed before filesystem work, so every second
|
|
`start()` call fails with `IllegalStateException`, including after a failed
|
|
first attempt. Queries check a volatile availability gate and fail with
|
|
`IllegalStateException` until startup has succeeded.
|
|
|
|
### Initial loading is all-or-nothing
|
|
|
|
Startup loads every runtime file into a private candidate Journal service using
|
|
the existing parser and ingestion rules. Only after the loader has completed
|
|
does the active object copy the candidate's immutable chains and digest index
|
|
under its state lock and open the availability gate. A root, layout, parse or
|
|
chain failure therefore propagates unchanged and leaves the active service with
|
|
no queryable partial worldview.
|
|
|
|
Extracting a separate new mutable chain-store abstraction was rejected because
|
|
it would broaden a lifecycle refactor into a Journal-domain redesign. The
|
|
candidate uses the already accepted ingestion implementation and is never
|
|
published.
|
|
|
|
### Filesystem loading belongs behind the service admin boundary
|
|
|
|
The existing strict loader logic moves from the manager into a package-private
|
|
`JournalFilesystemLoader`. The Journal process invokes it for initial loading;
|
|
the manager delegates refresh through `JournalServiceAdmin`. The loader keeps
|
|
the exact root/layout/readability checks, deterministic ordering, placement
|
|
validation, `.journal.example` exclusion and no-write behavior.
|
|
|
|
Post-start refresh loads into the active service and deliberately preserves the
|
|
existing per-ingestion atomicity and idempotency. The issue does not strengthen
|
|
refresh into whole-filesystem transactional replacement.
|
|
|
|
### Hub starts Journal as infrastructure before the business graph
|
|
|
|
After Registry start, registration and successful-history recording,
|
|
`NenjimHubImpl` creates the Journal assembly for `data/nenjim/journals`, retains
|
|
its manager, registers the service as `nenjim.journal.service`, starts its
|
|
process view, and records the ID only after `start()` returns. Business graph
|
|
construction and the existing enabled startup sequence occur only afterwards.
|
|
|
|
Consequently, a Journal failure propagates from the Hub while the Registry is
|
|
the sole recorded process. The Hub neither constructs/starts later business
|
|
processes nor reaches its online point or blocking wait. No rollback is added
|
|
because the common process contract has no stop operation.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- [The hidden implementation was formerly public] → No accepted public API
|
|
signature returns it; the assembly exposes only public interfaces and keeps
|
|
the documented implementation boundary intact.
|
|
- [Loading through a candidate duplicates temporary in-memory state] → Startup
|
|
happens once and Journal data is expected to be modest; the temporary cost
|
|
buys clear all-or-nothing publication without changing domain semantics.
|
|
- [Refresh can accept an earlier new revision before a later file fails] → This
|
|
is the already specified per-ingestion atomic behavior and is intentionally
|
|
preserved.
|
|
- [The Hub's successful path blocks indefinitely] → Verify Journal lifecycle,
|
|
identity, boundaries and sample-root loading with temporary probes rather
|
|
than invoking production `main()`.
|
|
|
|
## Migration Plan
|
|
|
|
1. Change Journal contracts and add the internal admin, loader and assembly
|
|
boundary.
|
|
2. Move startup into the service process and reduce the manager to passive
|
|
delegation.
|
|
3. Register/start Journal in the Hub before business composition and startup.
|
|
4. Update OpenSpec and directly affected documentation.
|
|
5. Run strict OpenSpec validation, the complete Gradle build and safe temporary
|
|
boundary/lifecycle probes, then leave this change active for review.
|