75: Implement the Nenjim Journal model, text parser, manager and filesystem service

This commit is contained in:
2026-08-20 12:28:16 +02:00
parent f29013e9c2
commit b230e15ece
43 changed files with 3303 additions and 276 deletions
@@ -0,0 +1,215 @@
# nenjim-journal-format Specification
## Purpose
Defines the observable format-version-1 contract and immutable domain meaning of a complete, artifact-scoped Nenjim Journal snapshot.
## Requirements
### Requirement: Artifact-scoped complete snapshots
The Journal format SHALL describe exactly one artifact identified by the coordinate `<GROUP>-<MODULE>-<ARTIFACT>`, SHALL contain no `JOURNAL_ID` or artifact `TYPE`, and SHALL represent a complete immutable worldview rather than a delta from its predecessor.
#### Scenario: Valid artifact identity
- **WHEN** metadata contains non-empty `GROUP=com_r35157_nenjim`, `MODULE=hubd`, and `ARTIFACT=api`
- **THEN** the parsed coordinate is `com_r35157_nenjim-hubd-api`
#### Scenario: Obsolete identity fields
- **WHEN** a Journal contains `JOURNAL_ID` or `TYPE`
- **THEN** parsing fails as an unknown or misplaced field without silently ignoring it
#### Scenario: No-op semantic revision
- **WHEN** a later valid snapshot differs from its predecessor only by `JOURNAL_VERSION` and `PREVIOUS_JOURNAL_DIGEST`
- **THEN** the snapshot remains valid because revisions are complete worldviews and need not change their semantic content
### Requirement: Strict top-level structure and comments
The parser SHALL require the logical order `FORMAT_VERSION=1`, `JOURNAL_VERSION`, optional `PREVIOUS_JOURNAL_DIGEST`, `[METADATA]`, optional `[POLICY]`, and one or more `[RELEASE <version>]` sections. Blank lines and full-line or trailing `#` comments SHALL be accepted, except that a `#` inside a quoted value is data.
#### Scenario: Comments before format entry
- **WHEN** blank lines and full-line comments precede `FORMAT_VERSION=1`
- **THEN** the format entry is treated as the first actual configuration entry and parsing proceeds
#### Scenario: Hash inside quoted text
- **WHEN** `DESCRIPTION='Project #1' # trailing comment` is parsed
- **THEN** the decoded description is `Project #1` and the trailing comment is discarded
#### Scenario: Unsupported format
- **WHEN** the first actual entry is `FORMAT_VERSION=2`
- **THEN** parsing fails explicitly because the format version is unsupported
#### Scenario: Misordered or duplicate structure
- **WHEN** a required entry or section is missing, duplicated, malformed, unknown, or placed outside its defined order
- **THEN** parsing fails and no unknown content is ignored
### Requirement: Journal and publication timestamps
`JOURNAL_VERSION` and `PUBLISHED_AT` SHALL each use a strict UTC `uuuuMMddHHmmssSSS'Z'` timestamp with millisecond precision and SHALL retain distinct revision-time and release-time meanings.
#### Scenario: Valid timestamp
- **WHEN** `20260819121530842Z` is supplied as a Journal or publication timestamp
- **THEN** it is represented as the corresponding immutable UTC instant/value
#### Scenario: Invalid timestamp
- **WHEN** a timestamp has missing milliseconds, a non-UTC suffix, an impossible calendar value, or trailing characters
- **THEN** parsing fails
### Requirement: Quoted metadata values
`[METADATA]` SHALL contain exactly one non-empty `GROUP`, `MODULE`, `ARTIFACT`, and single-quoted `DESCRIPTION`, and MAY contain at most one single-quoted `INFORMATION_URI`. The parser SHALL decode `\'` and `\\`, reject other malformed escaping and empty quoted values, and expose information URI as `Optional<URI>` only when the decoded value is a syntactically valid non-empty absolute URI, without restricting its scheme.
#### Scenario: Valid quoted metadata
- **WHEN** metadata contains `DESCRIPTION='Nenjim\'s reference hub'` and `INFORMATION_URI='ftp://ftp.domain.com/components/123.txt'`
- **THEN** the model exposes description `Nenjim's reference hub` and the exact absolute FTP URI
#### Scenario: Optional information URI absent
- **WHEN** valid metadata omits `INFORMATION_URI`
- **THEN** the model exposes an empty optional URI
#### Scenario: Scheme-independent URI
- **WHEN** a syntactically valid absolute `https:`, `ftp:`, or `ipfs:` URI is supplied
- **THEN** parsing accepts it without fetching, opening, or scheme-specific interpretation
#### Scenario: Invalid metadata text or URI
- **WHEN** a required quoted value is unquoted, empty, unterminated, badly escaped, or the information URI is relative or syntactically invalid
- **THEN** parsing fails at that field
### Requirement: Exact artifact releases and content
Every release section SHALL identify a unique exact `major.minor.patch` stable semantic version and SHALL contain exactly one non-empty quoted `LICENSE`, exactly one valid `PUBLISHED_AT`, one or more supported unique `CONTENT_DIGEST` values, and exactly one non-negative long `CONTENT_SIZE`.
#### Scenario: Valid release content
- **WHEN** release `1.0.3` contains decoded license text, a valid publication timestamp, a SHA-256 digest, an optional CIDv1 digest, and a non-negative content size
- **THEN** the immutable release model exposes all values without normalizing or interpreting the license
#### Scenario: Invalid release version
- **WHEN** a release heading uses `1`, `1.0`, `1.0.0-beta`, or `1.0.0+build.42`
- **THEN** parsing fails rather than normalizing it to another exact release
#### Scenario: Duplicate release
- **WHEN** two release sections identify the same exact version
- **THEN** parsing fails
#### Scenario: Invalid license
- **WHEN** a release omits `LICENSE`, repeats it, or supplies an unquoted, malformed, or empty value
- **THEN** parsing fails
#### Scenario: Digest validation
- **WHEN** content digests use valid `sha256:<64 lowercase hexadecimal characters>` and/or `cid1:<CIDv1 base32 text>` values whose payload is the exact canonical unpadded lowercase Base32 encoding of a valid CIDv1 envelope, with at most one digest of each scheme
- **THEN** all supplied content identities remain inspectable in the release model
#### Scenario: Canonical CIDv1 envelope
- **WHEN** a CIDv1 content digest is supplied
- **THEN** its version, multicodec, multihash code, and digest-length fields use their shortest unsigned varint encoding within the supported `long` range, and the declared digest length matches the payload
#### Scenario: Non-canonical CIDv1
- **WHEN** a CIDv1 spelling has non-zero unused Base32 padding bits, a non-minimal varint, or a varint that overflows the supported `long` range in any envelope field
- **THEN** parsing fails even if a permissive decoder could produce the same CID bytes or a truncated numeric value
#### Scenario: Invalid content identity
- **WHEN** no content digest exists, a digest scheme is unknown, a digest syntax is invalid, a scheme repeats, or content size is negative or outside the long range
- **THEN** parsing fails
### Requirement: Normalized version expressions
The model SHALL distinguish exact artifact versions from partial version boundaries, normalize semantically equivalent selector syntax, support membership and set containment, and reject unsupported or empty expressions.
#### Scenario: Equivalent exact forms
- **WHEN** `1.0.3` and `[1.0.3]` are parsed
- **THEN** both produce the same exact version-expression representation
#### Scenario: Minor series
- **WHEN** `[1.0]` is parsed
- **THEN** it contains every stable `1.0.x` release from `1.0.0` through the release immediately before `1.1.0` and is not normalized to exact `1.0.0`
#### Scenario: Inclusive and exclusive ranges
- **WHEN** `[1.0.3-->1.0.7]` and `[1.0.3-->1.0.7)` are parsed
- **THEN** the first contains both endpoints and the second excludes `1.0.7`
#### Scenario: Partial range boundaries
- **WHEN** `[1.0-->1.2]` or `[1.0-->1.2)` is parsed
- **THEN** the inclusive form contains the entire `1.2.x` series and the exclusive form excludes the entire `1.2.x` series
#### Scenario: Explicit cross-major range
- **WHEN** `[1.0-->3.0)` is parsed
- **THEN** it contains stable versions in major lines 1 and 2 but none in major line 3
#### Scenario: Open upper range
- **WHEN** `[1.0-->`, `(1.0-->`, or `[1.0.3-->` is parsed
- **THEN** its implicit exclusive upper boundary is `2.0.0`, while its lower boundary respectively starts at `1.0.0`, `1.1.0`, or `1.0.3`
#### Scenario: Expression union
- **WHEN** `[1.0.0-->1.0.3], [1.0.7], 1.1.1` is parsed for a repeatable/list-valued rule
- **THEN** the model represents the normalized union and accepts whitespace around commas and `-->`
#### Scenario: Invalid expression
- **WHEN** an expression uses a major-only boundary, a bare incomplete exact version, prerelease/build syntax, mathematical comparison syntax, whitespace inside a numeric version, an empty list element, reversed boundaries, or an empty range
- **THEN** parsing fails
### Requirement: Nenjim dependencies
Each artifact release SHALL model each `nenjim:<artifact-coordinate>` dependency target with exactly one `DEPENDS_ON` base expression set, zero or more described `EXCLUDES` entries, and zero or more described `PREFERRED` entries. Unknown schemes SHALL fail explicitly, exclusions and preferences SHALL require a corresponding base dependency, and the complete preferred set SHALL be contained by `DEPENDS_ON - EXCLUDES`.
#### Scenario: Valid dependency relationship
- **WHEN** a release depends on `nenjim:com_r35157_nenjim-hubd-api=[1.0-->`, excludes `1.0.4` with a description, and prefers `1.0.3`
- **THEN** the model preserves the target, base constraint, and individual described exclusion/preference rules
#### Scenario: Repeated exclusion and preference rules
- **WHEN** multiple `EXCLUDES` or `PREFERRED` lines refer to one declared target
- **THEN** their expression sets form unions while every line and optional description remains individually inspectable
#### Scenario: Duplicate base dependency
- **WHEN** the same dependency target has two `DEPENDS_ON` entries
- **THEN** parsing fails
#### Scenario: Orphan relationship rule
- **WHEN** an `EXCLUDES` or `PREFERRED` target has no corresponding `DEPENDS_ON`
- **THEN** parsing fails
#### Scenario: Invalid preferred subset
- **WHEN** any preferred version lies outside the base dependency set or inside an exclusion
- **THEN** parsing fails because preference cannot make an invalid version valid
#### Scenario: Unsupported dependency scheme
- **WHEN** a dependency uses `maven:` or another unsupported scheme in format version 1
- **THEN** parsing fails clearly rather than ignoring or resolving it
### Requirement: Artifact policy
An optional `[POLICY]` section SHALL preserve individual `BLACKLIST`, `DISCOURAGED`, and `RECOMMENDED` rules and descriptions. Blacklist SHALL be a hard constraint, discouraged and recommended SHALL be hints, overlaps SHALL be valid, and each recommended rule SHALL identify exactly one exact release present in the same snapshot. These recommendation invariants SHALL be enforced both while parsing and when the public model types are constructed directly without the parser.
#### Scenario: Valid overlapping policy
- **WHEN** a broad discouraged range overlaps a blacklisted exact release and an existing exact release is recommended
- **THEN** all individual rules and descriptions remain inspectable and the blacklist has the documented stronger meaning without flattening the rules
#### Scenario: Repeated policy entries
- **WHEN** policy contains several blacklist, discouraged, or recommended lines
- **THEN** all lines are retained as immutable individual rules
#### Scenario: Invalid recommendation
- **WHEN** `RECOMMENDED` contains a non-exact range or identifies a release absent from the snapshot
- **THEN** parsing fails
#### Scenario: Direct policy construction rejects a non-exact recommendation
- **WHEN** a caller constructs `JournalPolicy` directly with a recommended rule containing a range, a minor series, multiple versions, or any other non-exact `VersionExpressionSet`
- **THEN** construction fails because one recommended rule must identify exactly one exact `major.minor.patch` version
#### Scenario: Direct Journal construction rejects an absent recommendation
- **WHEN** a caller constructs `Journal` directly with an exact recommended version that is absent from that Journal's release map
- **THEN** construction fails because recommendations refer only to releases in the same snapshot
#### Scenario: Direct construction preserves valid policy overlaps
- **WHEN** a caller constructs a Journal with an existing exact release recommended while that version also belongs to discouraged or blacklist rules
- **THEN** construction succeeds and all individual rules remain inspectable because overlaps are valid
#### Scenario: Optional description decoding
- **WHEN** a repeatable policy or dependency rule ends with `:'Known \'issue\' on C:\\tmp'`
- **THEN** its decoded non-empty description is `Known 'issue' on C:\tmp`
#### Scenario: Invalid optional description
- **WHEN** an explicitly supplied description is empty, unquoted, unterminated, or has malformed escaping
- **THEN** parsing fails
### Requirement: Complete diagnostics and immutable results
Parsing SHALL validate the entire document before it is accepted, SHALL report the known logical source, one-based line number, and reason for every parse failure, and SHALL return immutable defensively copied objects and collections without using `null` for optional values.
#### Scenario: Source-aware failure
- **WHEN** line 12 of logical source `example.journal` contains an invalid field
- **THEN** the parse exception identifies `example.journal`, line 12, and the validation reason
#### Scenario: Immutable model
- **WHEN** callers obtain metadata, releases, policies, dependencies, digests, or expression collections
- **THEN** callers cannot mutate the Journal snapshot through those returned values
@@ -0,0 +1,163 @@
# 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 be the ordinary 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, or mutation operation.
#### Scenario: Chronological and latest queries
- **WHEN** an artifact has several accepted revisions
- **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
- **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 provides no start, stop, filesystem root, refresh, parsing, update, ingestion, 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: Journal service lifecycle manager
`JournalServiceManager` SHALL be the lifecycle and administrative API. It SHALL start and stop the service, report the normalized configured Journal data root, perform explicit refresh, and expose the corresponding `JournalService` only after successful initialization. It SHALL not duplicate the Journal query API.
#### Scenario: Successful initial start
- **WHEN** the configured root and all runtime Journal data are valid
- **THEN** start performs the complete initial chronological load before the manager reports success and exposes its read-only service
#### Scenario: Failed initial start
- **WHEN** the root, placement, filename, text, or chain is invalid during initial loading
- **THEN** start fails clearly and the manager does not report a started service or expose a partially loaded service
#### Scenario: Service access follows lifecycle
- **WHEN** service access or refresh is requested before successful start or after stop
- **THEN** the manager rejects the operation as unavailable in the current lifecycle state
#### Scenario: Stop is non-destructive
- **WHEN** the manager is stopped
- **THEN** service-owned maintenance stops cleanly without creating, rewriting, or deleting Journal files
#### Scenario: Administrative API does not duplicate queries
- **WHEN** `JournalServiceManager` is inspected
- **THEN** artifact and revision query operations are available only through its associated `JournalService`
### Requirement: Filesystem-backed lifecycle loading
The reference service manager SHALL own a configured existing readable root, 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 internal ingestion operation. It SHALL not 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 start or 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** start 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 service manager SHALL support explicit refresh. Repeating refresh for unchanged files SHALL be idempotent, and a later refresh SHALL discover valid newly added successors while preserving existing historical revisions.
#### Scenario: Unchanged refresh
- **WHEN** refresh runs twice 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 an earlier successful refresh
- **THEN** the next refresh ingests it and the read-only service exposes it as the new head
### 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