191 lines
15 KiB
Markdown
191 lines
15 KiB
Markdown
# nenjim-journal-management Specification
|
|
|
|
## Purpose
|
|
|
|
Defines the public Journal query service, its lifecycle manager, internal atomic chain ingestion, historical query behavior, and explicit filesystem loading of immutable Journal revisions.
|
|
## Requirements
|
|
### Requirement: Strict Journal API package boundary
|
|
The public Journal API SHALL begin at `com.r35157.nenjim.service.journal`, where the only directly contained Java types SHALL be the public interfaces `JournalService` and `JournalServiceManager`. Public composed domain objects SHALL be under `.model`, public value types SHALL be under `.valuetypes`, public Journal-specific exceptions SHALL be under `.exception`, and the reference implementation and its parser, ingestion, chain-state, and filesystem helpers SHALL be under `.impl.ref` or its subpackages without leaking implementation types through public signatures.
|
|
|
|
#### Scenario: Public package contents
|
|
- **WHEN** the compiled Journal API packages are inspected
|
|
- **THEN** only `JournalService` and `JournalServiceManager` are directly in `com.r35157.nenjim.service.journal`, while composed models, value types, and exceptions are in their respective public subpackages
|
|
|
|
#### Scenario: Reference implementation boundary
|
|
- **WHEN** the public service and model signatures are inspected
|
|
- **THEN** they expose no parser, mutable ingestion API, filesystem loader, chain store, or reference-implementation type
|
|
|
|
#### Scenario: Superseded API removed
|
|
- **WHEN** the source and compiled Journal API are inspected
|
|
- **THEN** no public `JournalManager`, deprecated alias, forwarding wrapper, or compatibility type remains under `com.r35157.nenjim.journal` or another superseded package
|
|
|
|
### 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 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: Internal atomic text ingestion and chain identity
|
|
The reference service implementation SHALL keep mutable ingestion non-public and filesystem-independent. One ingestion SHALL reject null, empty, or blank input; parse and validate the complete text; compute SHA-256 over its exact UTF-8 bytes including whitespace and line endings; validate its chain relationship; and atomically expose the immutable Journal through `JournalService`. A failed ingestion SHALL leave every previously accepted revision unchanged for that operation.
|
|
|
|
#### Scenario: Valid internal ingestion
|
|
- **WHEN** a complete valid genesis Journal is supplied internally
|
|
- **THEN** it becomes immediately queryable through the read-only service under its metadata-derived coordinate
|
|
|
|
#### Scenario: Exact text digest
|
|
- **WHEN** a Journal text is accepted
|
|
- **THEN** its revision digest is calculated from exactly the supplied UTF-8 text, including whitespace and line endings
|
|
|
|
#### Scenario: Failed ingestion is atomic
|
|
- **WHEN** an ingestion is null, empty, blank, structurally invalid, semantically invalid, or chain-invalid
|
|
- **THEN** the operation fails and every previously accepted chain and revision remains unchanged
|
|
|
|
### Requirement: Linear per-artifact Journal chains
|
|
Each artifact coordinate SHALL have exactly one genesis revision and one linear head. A non-genesis revision SHALL reference the current predecessor's exact SHA-256 digest and have a strictly greater Journal version. Missing, cross-artifact, stale-head, duplicate-genesis, or fork relationships SHALL fail.
|
|
|
|
#### Scenario: Valid successor
|
|
- **WHEN** a later revision for the same coordinate references the current head's exact text digest and has a strictly greater Journal version
|
|
- **THEN** it becomes the new head while the predecessor remains historically queryable
|
|
|
|
#### Scenario: Missing or cross-artifact predecessor
|
|
- **WHEN** a non-genesis revision references an unknown digest or a digest owned by another artifact coordinate
|
|
- **THEN** ingestion fails without changing either chain
|
|
|
|
#### Scenario: Fork or stale predecessor
|
|
- **WHEN** a revision references a known non-head predecessor or attempts another genesis for an existing coordinate
|
|
- **THEN** ingestion fails rather than creating multiple heads
|
|
|
|
#### Scenario: Non-increasing revision time
|
|
- **WHEN** a successor Journal version is equal to or earlier than its predecessor's version
|
|
- **THEN** ingestion fails
|
|
|
|
### Requirement: Idempotency and conflicts
|
|
Supplying the same coordinate, Journal version, and exact complete text more than once SHALL be idempotent. Supplying different text for an existing coordinate and Journal version SHALL fail as a conflict even if the semantic model would be equal.
|
|
|
|
#### Scenario: Repeated identical text
|
|
- **WHEN** an already accepted complete text is supplied again
|
|
- **THEN** the existing immutable revision is returned without changing the chain
|
|
|
|
#### Scenario: Conflicting text
|
|
- **WHEN** different text is supplied for an already stored coordinate and Journal version
|
|
- **THEN** ingestion reports a conflict and does not mutate state
|
|
|
|
### 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
|
|
|
|
### Requirement: Version-controlled examples and migration
|
|
Version-controlled examples SHALL use `<journal-version>.journal.example` beneath an artifact-coordinate directory and collectively demonstrate quoted description, a scheme-independent information URI, per-release license, dependencies, exclusions, preferences, policy, exact/range syntax, and optional descriptions. Any multi-revision example chain SHALL use the predecessor file's real exact-text SHA-256 digest.
|
|
|
|
#### Scenario: Example placement
|
|
- **WHEN** repository Journal examples are inspected
|
|
- **THEN** each is placed under `data/nenjim/journals/<artifact-coordinate>/` with a strict Journal-version filename ending in `.journal.example`
|
|
|
|
#### Scenario: Example chain integrity
|
|
- **WHEN** examples include a successor revision
|
|
- **THEN** its predecessor digest equals the SHA-256 of the predecessor example file's exact UTF-8 bytes
|
|
|
|
### 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
|