Files
com_r35157_nenjim-hubd-impl…/openspec/specs/nenjim-journal-management/spec.md
T

15 KiB

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