Implement the Nenjim Journal model, text parser, query service and lifecycle manager #75

Open
opened 2026-08-19 09:33:45 +02:00 by minimons · 0 comments
Owner

Title: Implement the Nenjim Journal model, text parser, query service and lifecycle manager

Background

Nenjim Journals need to become a stable, executable domain foundation before Context and classloader work continues.

The current branch contains preliminary artifact-scoped Journal examples and obsolete Java skeletons. The Journal design has since been refined substantially. This issue must consolidate those decisions into:

  • A documented Journal format.
  • Immutable Java domain objects.
  • A strict text parser and validator.
  • A filesystem-independent, read-only JournalService for ordinary consumers.
  • A filesystem-facing JournalServiceManager for lifecycle and administration.
  • A strict public API/implementation package boundary.
  • Query APIs for inspecting current and historical Journal knowledge.

The branch may also contain an intermediate implementation of issue #75 under com.r35157.nenjim.journal. That package layout and its concrete public JournalManager/JournalService split are superseded by the Service/ServiceManager architecture defined below. The intermediate implementation must be refactored without compatibility aliases.

Parsing is intentionally included. Many Journal invariants only become real contracts when invalid text is rejected rather than merely described in documentation.

This issue does not implement dependency resolution, Contexts, classloading or artifact downloading.

Core Journal model

One Journal per artifact

A Journal describes exactly one artifact.

API, test and implementation artifacts therefore have separate Journals, for example:

com_r35157_nenjim-hubd-api
com_r35157_nenjim-hubd-tests
com_r35157_nenjim-hubd-impl_ref

Artifact roles such as API, IMPLEMENTATION and TEST are not part of the Journal format. There is no TYPE field.

The artifact coordinate is derived from:

<GROUP>-<MODULE>-<ARTIFACT>

The complete reference to an artifact release is:

<GROUP>-<MODULE>-<ARTIFACT>:<VERSION>

Example:

com_r35157_nenjim-hubd-api:1.0.3

There is no separate JOURNAL_ID. The coordinate in [METADATA] identifies the artifact and its Journal chain.

Journal revisions are complete snapshots

Each Journal file is an immutable, complete snapshot of all current knowledge about one artifact and all its known releases.

A revision is not a delta. Nothing is inherited implicitly from its predecessor.

Once published, a Journal file is never changed. New knowledge is published as a new Journal revision containing the complete resulting worldview.

A newer Journal revision may, without publishing a new artifact release:

  • Change DESCRIPTION.
  • Change INFORMATION_URI.
  • Add or remove known releases.
  • Correct information about an existing release.
  • Correct or change the recorded LICENSE for an existing release.
  • Change DEPENDS_ON, EXCLUDES or PREFERRED.
  • Change RECOMMENDED, DISCOURAGED or BLACKLIST.
  • Contain exactly the same semantic worldview as its predecessor.

A no-op revision is valid. It may differ only in:

JOURNAL_VERSION
PREVIOUS_JOURNAL_DIGEST

Historical queries must return the complete worldview contained in the requested revision. Revisions must never be merged retroactively.

Journal time versus release time

JOURNAL_VERSION identifies when a Journal worldview was published.

PUBLISHED_AT identifies when an artifact release was published.

They are separate concepts. Historical knowledge must be determined from the Journal chain, not by filtering the newest snapshot on PUBLISHED_AT.

Both use UTC timestamps with millisecond precision:

uuuuMMddHHmmssSSS'Z'

Example:

20260819121530842Z

Journal chain

The first revision in an artifact’s Journal chain omits PREVIOUS_JOURNAL_DIGEST.

Every later revision must contain:

PREVIOUS_JOURNAL_DIGEST=sha256:<digest>

The digest is the SHA-256 digest of the predecessor’s complete UTF-8 Journal text exactly as ingested by the reference implementation, including whitespace and line endings.

For each artifact coordinate:

  • There must be exactly one genesis revision.
  • Every non-genesis revision must reference the current predecessor.
  • JOURNAL_VERSION must be strictly greater than the predecessor’s version.
  • Forks and multiple chain heads are invalid.
  • A missing predecessor is invalid.
  • A predecessor from another artifact coordinate is invalid.
  • Supplying the same revision with identical text is idempotent.
  • Supplying different text for an existing coordinate and JOURNAL_VERSION is a conflict.

The internal ingestion operation may require predecessors to have been supplied first. JournalServiceManagerImpl must therefore load files chronologically within each artifact directory.

Journal format version 1

A complete non-genesis Journal may look like this:

FORMAT_VERSION=1

JOURNAL_VERSION=20260819121530842Z
PREVIOUS_JOURNAL_DIGEST=sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

[METADATA]
GROUP=com_r35157_nenjim
MODULE=hubd
ARTIFACT=impl_ref
DESCRIPTION='The reference implementation of the Nenjim Hub.'
INFORMATION_URI='ftp://ftp.domain.com/components/123.txt'

[POLICY]
RECOMMENDED=1.0.3:'Best tested release'
DISCOURAGED=[1.0.0-->1.0.2],1.0.7:'Known performance degradation'
BLACKLIST=1.0.5:'Security issue CVE-1234'

[RELEASE 1.0.3]
LICENSE='Public Domain'
PUBLISHED_AT=20260815193000000Z
CONTENT_DIGEST=sha256:123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0
CONTENT_DIGEST=cid1:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a
CONTENT_SIZE=234567
DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0-->
EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=[1.0.4-->1.0.5]:'Known incompatibility'
PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3

General parsing rules

  • The input is UTF-8 text.
  • FORMAT_VERSION=1 must be the first actual configuration entry.
  • Blank lines and full-line comments beginning with # may precede it.
  • Blank lines and full-line comments are otherwise allowed throughout the file.
  • Full-line and trailing comments beginning with # are allowed.
  • A # inside a single-quoted value is part of the value and does not begin a comment.
  • Unknown, malformed, duplicate or misplaced sections and fields must fail parsing.
  • An unsupported format version must fail explicitly.
  • Empty required values must fail.
  • Unknown fields must never be silently ignored.
  • Parsing errors must identify the input source when known, the line number and the reason.
  • The parser must validate the entire Journal before the reference implementation mutates its internal state.
  • Returned domain objects and collections must be immutable and defensively copied.

Required top-level structure

The logical section order is:

FORMAT_VERSION
JOURNAL_VERSION
optional PREVIOUS_JOURNAL_DIGEST
[METADATA]
optional [POLICY]
one or more [RELEASE <version>] sections

Required [METADATA] fields:

GROUP
MODULE
ARTIFACT
DESCRIPTION

DESCRIPTION applies to the artifact and may change in a later Journal revision.

DESCRIPTION is required exactly once and its value must be enclosed in single quotes:

DESCRIPTION='The public API for Nenjim Hub.'

The parser stores the decoded text value without the surrounding quotes.

[METADATA] may additionally contain one optional INFORMATION_URI:

INFORMATION_URI='ftp://ftp.domain.com/components/123.txt'

INFORMATION_URI is the primary address at which a person or tool can find more information about the artifact or
the project/module to which it belongs. Related artifact Journals may therefore repeat the same URI.

Rules for INFORMATION_URI:

  • It is optional and may occur at most once.
  • Its value must be enclosed in single quotes.
  • Its decoded value must be a syntactically valid, non-empty absolute URI.
  • Format version 1 does not restrict the URI scheme. Values such as https:, ftp: and ipfs: are allowed.
  • The Journal model must store it as an optional java.net.URI, not merely as an unvalidated string.
  • The parser stores only the raw URI represented by the quoted value. Markdown link syntax such as [text](URI) is not part of the Journal format.
  • Nenjim must not automatically open, fetch or otherwise interpret the referenced content in this issue.
  • A later Journal revision may change or remove the URI without publishing a new artifact release.

The following obsolete concepts must be rejected:

JOURNAL_ID
TYPE

Artifact release versions

Every [RELEASE ...] heading contains one exact Semantic Version:

major.minor.patch

Examples:

0.1.0
1.0.3
12.45.600

Release versions must contain all three numeric components.

The following are not artifact release versions:

1
1.0

Prereleases and build metadata are intentionally unsupported in format version 1:

1.0.0-beta
1.0.0-rc.1
1.0.0+build.42

Release versions must be unique within a Journal snapshot.

The existing SemanticVersion type may be reused for exact releases, but the Journal parser must never normalize an incomplete 1.0 release into 1.0.0.

Release content

Each release contains:

LICENSE
PUBLISHED_AT
one or more CONTENT_DIGEST entries
CONTENT_SIZE

LICENSE records the license under which that particular artifact release is made available:

[RELEASE 0.1.0]
LICENSE='Public Domain'
PUBLISHED_AT=20260815193000000Z
CONTENT_DIGEST=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
CONTENT_DIGEST=cid1:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a
CONTENT_SIZE=123456

Rules for LICENSE:

  • Every release must contain exactly one LICENSE field.
  • Its value must be enclosed in single quotes.
  • The parser must reject a missing, duplicate, malformed or empty LICENSE value.
  • Format version 1 treats the decoded value as unrestricted text and stores it in the ArtifactRelease model.
  • The parser must not require a known license identifier, normalize the value or interpret its legal meaning.
  • Different releases in the same Journal may use different license values.
  • Future Nenjim functionality may filter releases by license, but license filtering is outside this issue.

Supported content digest schemes in format version 1 are:

sha256
cid1

Rules:

  • At least one supported content digest is required.
  • A release may contain both SHA-256 and CIDv1 digests.
  • There may be at most one digest of each type.
  • Digest syntax must be validated.
  • A CIDv1 value must use the exact canonical unpadded lowercase Base32 spelling of its decoded bytes.
  • The CID version, multicodec, multihash code and digest-length fields must use their shortest unsigned varint encodings within the supported non-negative long range.
  • Non-zero unused Base32 padding bits, non-minimal varints, overflow, unterminated varints and a digest length that does not match the payload must fail.
  • Unknown digest types must fail.
  • CONTENT_SIZE is the artifact size in bytes and must be a valid non-negative long.
  • Content retrieval, download and digest verification of downloaded artifacts are outside this issue.
  • Different Journal revisions may repeat the same release and content information.
  • Separate artifact releases are allowed to identify identical content bytes.

CONTENT_DIGEST replaces the earlier idea of CONTENT_TYPE. Content providers will later decide how the bytes are retrieved.

Version-expression model

Artifact versions and version expressions are different domain concepts:

SemanticVersion   = one exact artifact release
VersionExpression = a set of acceptable artifact releases

A version-expression endpoint must contain at least:

major.minor

A major-only expression is invalid.

Exact versions

These expressions are equivalent and must produce the same Java representation:

1.0.3
[1.0.3]

A bare exact value must always contain major.minor.patch.

Minor series

[1.0]

means every stable 1.0.x release:

>= 1.0.0 and < 1.1.0

It must not be normalized into the exact version 1.0.0.

Closed ranges

Ranges use -->. Square brackets include a boundary and parentheses exclude it.

[1.0.3-->1.0.7]

includes both endpoints.

[1.0.3-->1.0.7)

excludes 1.0.7.

When an endpoint contains only major.minor, it denotes the whole minor series at that boundary.

Therefore:

[1.0-->1.2]

includes all 1.0.x, 1.1.x and 1.2.x releases.

[1.0-->1.2)

excludes the entire 1.2.x series.

An explicitly cross-major range is allowed:

[1.0-->3.0)

It includes major versions 1 and 2 but not major version 3. Crossing a major boundary must always be explicit.

Open upper ranges

An omitted upper boundary stops before the next major version:

[1.0-->

means:

>= 1.0.0 and < 2.0.0
(1.0-->

means:

>= 1.1.0 and < 2.0.0
[1.0.3-->

means:

>= 1.0.3 and < 2.0.0

Unsupported comparison syntax

Mathematical comparison operators are not supported:

<
<=
>
>=
=

An expression such as >=1.0 is dangerous because its ordinary meaning crosses into potentially incompatible major versions. Only exact selectors and Nenjim ranges are accepted.

Expression lists

Where a field supports several expressions, expressions are separated by commas and represent their union:

[1.0.0-->1.0.3],[1.0.7],1.1.1

Whitespace may appear around commas and -->, but not inside a numeric version.

Empty elements, reversed boundaries and empty ranges must fail.

The Java model must normalize semantically equivalent syntax. It does not need to preserve whether an exact version was originally written as 1.0.3 or [1.0.3].

Dependencies

Dependencies belong to individual artifact releases.

Every dependency uses an explicit scheme:

<scheme>:<scheme-specific-coordinate>

Format version 1 implements Nenjim dependencies:

nenjim:com_r35157_nenjim-hubd-api

The scheme is lowercase.

Future schemes may include Maven:

maven:org.apache.commons:commons-collections4:4.5.0

Maven dependency handling is outside this issue. Unknown or unsupported schemes must produce a clear error and must never be ignored.

DEPENDS_ON

A release may declare several dependencies, but the same dependency target may have exactly one DEPENDS_ON entry:

DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0-->

DEPENDS_ON defines the base set of acceptable versions.

Dependency EXCLUDES

EXCLUDES removes versions from one particular dependency relationship:

EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=[1.0.3-->1.0.5]
EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=1.0.7:'Known incompatibility'

This does not claim that the dependency releases are generally defective. It only states that this artifact release must not be combined with them.

Rules:

  • EXCLUDES is a hard constraint.
  • It requires a corresponding DEPENDS_ON.
  • It may occur multiple times for the same target.
  • A line may contain several comma-separated version expressions.
  • Repeated exclusion sets are combined as a union.
  • The optional description applies to every expression on that line.

Dependency PREFERRED

PREFERRED identifies a preferred subset of the dependency’s permitted versions:

PREFERRED=nenjim:com_r35157_nenjim-hubd-api=[1.0.7]

Rules:

  • PREFERRED is a hint, not a hard constraint.
  • It requires a corresponding DEPENDS_ON.
  • It may occur multiple times for the same target.
  • Repeated preferred sets are combined as a union.
  • The complete preferred set must be a subset of:
DEPENDS_ON - EXCLUDES

A preferred version must never make an otherwise invalid version valid.

The Journal layer only represents these rules. It does not select a dependency graph.

Artifact policy

[POLICY] describes policy for releases of the artifact described by the current Journal.

Policy rules may be changed in later Journal revisions without publishing new artifact content.

BLACKLIST

BLACKLIST=1.0.0:'Security issue CVE-1234'
BLACKLIST=[1.1.0-->1.1.3]:'Corrupt release contents'

BLACKLIST is a hard global rule: matching releases must not be selected.

It may occur multiple times and may contain comma-separated exact versions or ranges.

DISCOURAGED

DISCOURAGED=[1.0.1-->1.0.3],[1.0.7],1.1.1
DISCOURAGED=1.0.7:'Major performance issue'

DISCOURAGED is a negative hint. Matching releases should be avoided when a better usable choice exists, but they remain selectable.

It may occur multiple times and may contain comma-separated exact versions or ranges.

RECOMMENDED

RECOMMENDED=1.0.3:'Best tested release'

RECOMMENDED is a positive hint identifying a particularly well-tested release.

Rules:

  • Each entry identifies one exact release.
  • 1.0.3 and [1.0.3] are equivalent.
  • Ranges are not allowed.
  • The identified release must exist in the same Journal snapshot.
  • The field may be repeated, allowing separate supported major lines to have their own recommended release.
  • It is a hint, not a hard eligibility rule.

For example:

1.0.2
1.0.3  RECOMMENDED
1.0.4

A resolver should normally prefer 1.0.3. If it cannot or does not want to use that release—for example because it is offline and only has 1.0.4 locally—it may automatically select the highest otherwise valid release, here 1.0.4.

When 1.0.3 later becomes available, the Hub may prefer it even though changing from 1.0.4 to 1.0.3 is numerically a downgrade.

The desired long-term state is a valid graph containing as many compatible recommended components as possible. Highest version is a fallback, not always the primary goal.

The actual resolution, reconciliation and live upgrade/downgrade behavior belong to later Context and NenjimHub work.

Policy overlap

Overlapping policy rules are valid.

This permits a broad range to be described without splitting it merely because one release inside it has a stronger policy:

DISCOURAGED=[1.0-->1.1]
BLACKLIST=1.0.7:'Security vulnerability'

Hard rules always win over hints:

BLACKLIST > DISCOURAGED / RECOMMENDED

The Java model must preserve individual policy entries and their descriptions. It must not flatten everything into an unexplained set of versions.

Optional rule descriptions

The following repeatable rules may have an optional description:

BLACKLIST
DISCOURAGED
RECOMMENDED
EXCLUDES
PREFERRED

Syntax:

<rule-value>:'Description'

The description applies to every expression on the line.

Descriptions use single quotes. Format version 1 must support escaping at least:

\'   embedded single quote
\\   embedded backslash

A malformed, unterminated or empty explicitly supplied description must fail.

The same single-quoted text syntax and escaping rules apply to mandatory metadata DESCRIPTION, optional metadata
INFORMATION_URI and release LICENSE values. The Java model stores their decoded contents without the surrounding
quotes or escape characters.

Service terminology and responsibility split

Nenjim distinguishes the daily application-facing service API from the lifecycle and administrative API that owns and maintains that service.

Service

A Service is the public domain API used during ordinary operation by applications and other services.

A service interface:

  • Exposes the domain operations that ordinary consumers need.
  • Must not expose lifecycle operations such as start or stop.
  • Must not expose configuration, data-source administration, refresh or mutation of the service’s internal knowledge.
  • Must not expose implementation helpers, parsers or mutable storage.

For Journals, this interface is JournalService.

ServiceManager

A ServiceManager is the lifecycle and administrative API used by NenjimHub or another composition/lifecycle owner.

A service-manager interface:

  • Starts and stops the service.
  • Owns configuration and reports where the service’s data is located.
  • Performs explicit refresh and maintenance of the service’s internal knowledge.
  • Owns and exposes the corresponding daily service interface after successful initialization.
  • Is not the interface supplied to ordinary consumers for daily domain queries.

For Journals, this interface is JournalServiceManager.

These definitions must be added to docs/Terminologi.md and used consistently throughout the Nenjim documentation. This issue applies the terminology only to the Journal subsystem and must not change unrelated services.

Java packages and public API boundary

The public Journal service package is:

com.r35157.nenjim.service.journal

Only the two central public interfaces live directly in that package:

JournalService
JournalServiceManager

Public immutable Journal model objects live under:

com.r35157.nenjim.service.journal.model

This package contains at least:

Journal
JournalMetadata
JournalPolicy
ArtifactRelease
ArtifactDependency
PolicyRule

Public value types live under:

com.r35157.nenjim.service.journal.valuetypes

This package contains at least:

ArtifactCoordinate
JournalVersion
ContentDigest
DependencyTarget
VersionExpression
VersionExpressionSet

Public Journal-specific exceptions live under:

com.r35157.nenjim.service.journal.exception

This package contains at least:

InvalidJournalException

The reference implementation lives under:

com.r35157.nenjim.service.journal.impl.ref

It contains at least:

JournalServiceImpl
JournalServiceManagerImpl
JournalTextParser

Any parser contract, mutable ingestion API, chain store, filesystem loader or other implementation helper must remain in the reference-implementation package or a subpackage. Such implementation types must not leak into public service or model signatures.

Requirements for the public model:

  • Use immutable records/classes where appropriate.
  • Use Instant or semantically specific immutable value objects instead of java.util.Date.
  • Do not return null for missing query results.
  • Use immutable collections and defensive copies.
  • Preserve separate policy rules and descriptions.
  • Version expressions must support membership and set-containment operations required for validation.
  • Exact artifact versions and partial version boundaries must remain different types or otherwise retain their different semantics.
  • Journal or its metadata model must expose INFORMATION_URI as an optional java.net.URI.
  • ArtifactRelease must expose the decoded LICENSE text exactly as represented semantically by the Journal; it must not use an enum or a fixed list of licenses.
  • Journal represents one complete parsed revision.

The fact that many public model types are Java records does not make all of them value types. Complete domain snapshots and composed domain structures belong in model; small types defined completely by their value belong in valuetypes.

Validation ownership

Validation is deliberately distributed by responsibility:

  • JournalTextParser validates textual syntax, section and field ordering, quoting, comments, duplicates, source/line diagnostics and conversion from text.
  • Public model records/classes and value types validate their own construction invariants so invalid public domain objects cannot be created by bypassing the parser.
  • JournalServiceImpl validates cross-revision chain relationships and maintains the immutable in-memory Journal knowledge exposed through JournalService.
  • JournalServiceManagerImpl validates filesystem placement and naming and controls lifecycle, loading and refresh.

Complex constructor validation that protects a public model invariant may remain with the public record/class. Parsing algorithms and filesystem-specific rules must remain implementation details.

JournalService

JournalService is the filesystem-independent, read-only API used by ordinary consumers to query the Journal knowledge currently maintained by the service manager.

It must provide operations sufficient to:

  • List known artifact coordinates.
  • List all revisions for an artifact in chronological order.
  • Obtain the latest revision for an artifact.
  • Obtain an exact revision by artifact coordinate and JournalVersion.
  • Obtain the newest revision at or before a supplied JournalVersion.
  • Obtain an exact artifact release from a selected revision.
  • Inspect the optional information URI through the immutable Journal metadata model.
  • Inspect the license recorded for an artifact release through the immutable release model.
  • Inspect dependencies and policy through the immutable model.
  • Distinguish “not found” without returning null.

JournalService must not expose:

  • start, stop or lifecycle state changes.
  • Filesystem paths or data-source configuration.
  • Refresh/reload operations.
  • update(String) or any other way for ordinary consumers to mutate Journal knowledge.
  • Parsers or implementation state.

The service does not resolve dependencies or select releases.

JournalServiceManager

JournalServiceManager is the lifecycle and administrative interface for the Journal service.

It must support operations sufficient to:

  • Start the service and perform its initial Journal loading.
  • Stop service-owned maintenance cleanly.
  • Report the configured Journal data root as a normalized java.nio.file.Path.
  • Expose the corresponding JournalService for ordinary read-only use.
  • Perform an explicit refresh/reload.

The manager must not duplicate the Journal query API. Ordinary consumers receive JournalService, not JournalServiceManager.

JournalServiceManagerImpl is the filesystem-facing reference implementation. It owns a JournalServiceImpl and is responsible for:

  1. Finding Journal files on disk.
  2. Reading each complete file as UTF-8 text.
  3. Validating file placement and naming.
  4. Sorting each artifact’s revisions chronologically.
  5. Supplying each complete textual Journal to an internal, non-public ingestion operation on the reference implementation.
  6. Making the resulting knowledge available through the owned JournalService.

The initial start must fail clearly if the configured root or Journal data is invalid. The manager must not report a successful start until the initial loading operation has completed successfully. Stopping must not delete, rewrite or otherwise mutate Journal files.

The reference implementation’s internal ingestion operation must:

  1. Reject null, empty or blank input.
  2. Parse the complete text.
  3. Validate all structural and semantic invariants.
  4. Calculate the Journal’s SHA-256 digest from the exact UTF-8 text.
  5. Validate the predecessor relationship.
  6. Update the in-memory state atomically for that Journal revision.
  7. Make the resulting immutable Journal immediately queryable through JournalService.

A failed ingestion must leave the previously accepted in-memory state unchanged for that operation. Supplying identical text again is idempotent; supplying conflicting text for the same coordinate and Journal version must fail.

The reference implementation’s in-memory Journal state and ingestion logic must remain filesystem-independent. Filesystem access belongs only to JournalServiceManagerImpl.

Repeated refreshes must be idempotent for unchanged files and must discover newly added revisions. Filesystem watching, background polling and remote synchronization are future ServiceManager responsibilities but remain outside this issue.

Filesystem layout

Runtime Journals use:

data/nenjim/journals/
└── <artifact-coordinate>/
    ├── <journal-version>.journal
    └── <newer-journal-version>.journal

Example:

data/nenjim/journals/
└── com_r35157_nenjim-hubd-api/
    ├── 20260816201156858Z.journal
    └── 20260819121530842Z.journal

Version-controlled examples use:

<journal-version>.journal.example

Example:

data/nenjim/journals/com_r35157_nenjim-hubd-api/20260816201156858Z.journal.example

Rules:

  • Directory name must equal the coordinate derived from [METADATA].
  • Runtime filename without .journal must equal JOURNAL_VERSION.
  • Existing files are immutable and must never be overwritten by JournalServiceManagerImpl.
  • .journal.example files are documentation/examples and must not be loaded as runtime Journals.
  • JournalServiceManagerImpl reads only the defined artifact-directory/revision-file structure.
  • A missing or unreadable configured Journal root must fail clearly; the manager implementation must not create it silently.
  • File downloading, remote synchronization and deletion are outside this issue.

Move and update the current flat example files to this structure.

At least one example should demonstrate:

  • Single-quoted metadata DESCRIPTION.
  • An optional single-quoted INFORMATION_URI, including a valid non-HTTP scheme.
  • A single-quoted LICENSE for every release.
  • Dependencies.
  • Dependency exclusions and preferences.
  • Policy.
  • Exact and range syntax.
  • Optional descriptions.

If examples include multiple revisions in one chain, their PREVIOUS_JOURNAL_DIGEST values must be real digests of the predecessor example text.

Refactor of the intermediate issue #75 implementation

If the current branch already contains the first issue #75 implementation under:

com.r35157.nenjim.journal
com.r35157.nenjim.journal.impl.ref

it must be migrated completely to the package and responsibility structure defined in this issue.

In particular:

  • Move the current public JournalManager query operations to the new public read-only JournalService interface; no public JournalManager remains.
  • Put the filesystem-independent in-memory query and chain-state implementation behind JournalServiceImpl.
  • Replace the current concrete filesystem-facing JournalService with the public lifecycle/administrative JournalServiceManager interface and the reference JournalServiceManagerImpl.
  • Keep mutable ingestion and chain-state operations internal to the reference implementation.
  • Move public domain models, value types and exceptions into their specified subpackages.
  • Move parser contracts and implementations out of the public API packages.
  • Update all imports, documentation and OpenSpec artifacts.
  • Do not retain deprecated aliases, forwarding wrappers or compatibility types in the superseded packages.

Legacy cleanup

Remove the obsolete Journal/module skeletons when their references have been removed or updated:

com.r35157.nenjim.hubd.journal.Journal
com.r35157.nenjim.hubd.journal.JournalManager
com.r35157.nenjim.hubd.impl.ref.JournalManagerImpl
com.r35157.nenjim.hubd.module.Module
com.r35157.nenjim.hubd.module.Release
com.r35157.nenjim.hubd.module.Dependency
com.r35157.nenjim.valuetypes.journal.JournalId

Remove the hardcoded dummy getRelease(...) behavior.

Update obsolete imports and dead references so the project compiles, but do not integrate the new Journal service into Context or classloading in this issue.

Documentation

Update at least:

docs/Nenjim.md
docs/Terminologi.md
docs/Nenjim-public.html

The documentation must describe:

  • One Journal per artifact.
  • Artifact coordinates.
  • Optional scheme-independent INFORMATION_URI metadata.
  • Complete immutable snapshots.
  • Journal chains and historical worldview.
  • Journal revision time versus artifact publication time.
  • Content digests.
  • Per-release license text and its future role as filterable metadata.
  • Version-expression semantics.
  • Dependency schemes.
  • DEPENDS_ON, dependency EXCLUDES and PREFERRED.
  • [POLICY], BLACKLIST, DISCOURAGED and RECOMMENDED.
  • The distinction between hard constraints and hints.
  • Recommended-version fallback and possible later convergence/downgrade.
  • Filesystem layout.
  • The general distinction between a daily application-facing Service and its lifecycle/administrative ServiceManager.
  • JournalService as the read-only daily query API.
  • JournalServiceManager as the lifecycle, data-location and refresh API.
  • The separation between public model, valuetypes and exception packages and the reference implementation.
  • Which parts are implemented now and which remain future vision.

Remove or rewrite the currently obsolete documentation claiming that:

  • One Journal contains several artifacts.
  • JournalId is stored explicitly.
  • Artifact roles are stored as types.
  • A resolver always chooses the numerically highest version.

OpenSpec

Create an OpenSpec change for issue #75 covering:

  • Journal domain model.
  • Journal text format version 1.
  • Parsing and modelling of single-quoted DESCRIPTION, optional INFORMATION_URI and LICENSE values.
  • Parser and validation behavior.
  • Journal-chain management.
  • Filesystem-independent, read-only JournalService.
  • Filesystem-backed lifecycle and administrative JournalServiceManager.
  • Internal non-public parsing, ingestion and chain-state behavior.
  • Public API, model, value-type, exception and reference-implementation package boundaries.
  • Service and ServiceManager terminology in docs/Terminologi.md.
  • Query behavior.
  • Documentation and example migration.
  • Legacy cleanup.

The OpenSpec scenarios must include valid and invalid examples for the important parsing and validation rules, even though automated tests are currently deferred.

Leave the change active for review. Do not sync or archive it as part of implementation.

Out of scope

This issue must not implement:

  • Context or resolved Context objects.
  • Dependency-graph resolution.
  • Automatic release selection.
  • Runtime reconciliation, upgrade or downgrade.
  • Classloader behavior.
  • Artifact downloading.
  • IPFS retrieval.
  • Maven dependency resolution.
  • Signature generation or validation.
  • Journal publishing.
  • Remote Journal synchronization.
  • Filesystem watching or background polling.
  • UI or CLI management.
  • Opening, downloading or interpreting INFORMATION_URI content and scheme-specific URI behavior.
  • License-name normalization, license compatibility decisions or filtering releases by license.
  • Changes to unrelated services.
  • Wiring JournalServiceManager into Context, classloading or NenjimHub startup orchestration.
  • Automated unit or integration tests; these remain deferred until the planned Nenjim testing work.

Acceptance criteria

  • The latest project compiles successfully.
  • The only types directly in com.r35157.nenjim.service.journal are the public JournalService and JournalServiceManager interfaces.
  • Public composed Journal domain objects are under com.r35157.nenjim.service.journal.model.
  • Public Journal value types are under com.r35157.nenjim.service.journal.valuetypes.
  • Public Journal-specific exceptions are under com.r35157.nenjim.service.journal.exception.
  • Reference implementation, parser, mutable ingestion and filesystem logic are under com.r35157.nenjim.service.journal.impl.ref or its subpackages and do not leak into public signatures.
  • The model represents every field and rule defined above without loss of meaning.
  • Metadata DESCRIPTION is required in single quotes, is decoded by the parser and is available from the model.
  • Metadata INFORMATION_URI is optional, single-quoted, validated as an absolute URI and exposed as Optional<URI> without restricting its scheme.
  • Every artifact release has one required single-quoted LICENSE value which is decoded and available from ArtifactRelease without vocabulary validation or normalization.
  • CIDv1 content identities require canonical unpadded lowercase Base32, shortest non-overflowing unsigned varints and a matching digest payload length.
  • Invalid format versions, timestamps, versions, ranges, digests, policies, dependencies and chain relationships fail clearly.
  • JournalService is a filesystem-independent, read-only query interface with no lifecycle, data-location, refresh or mutation operations.
  • JournalServiceManager is the lifecycle and administrative interface for start, stop, data location, explicit refresh and access to the corresponding JournalService.
  • JournalServiceImpl maintains the in-memory Journal chains and serves current and historical queries without exposing public mutation.
  • JournalServiceManagerImpl reads the documented disk layout chronologically and supplies complete strings to the internal ingestion operation.
  • Internal ingestion parses, validates and atomically stores each complete Journal snapshot; failed ingestion leaves previously accepted state unchanged.
  • Initial manager start fails clearly for an invalid root or invalid Journal data and is not reported as successfully started before initial loading completes.
  • Journal queries support current and historical revisions.
  • Repeated identical updates are idempotent.
  • Conflicting revisions and chain forks fail.
  • Policy rules and their descriptions remain individually inspectable.
  • Current example Journals are moved to the revision-directory layout and updated to format version 1.
  • Obsolete Journal/module skeletons and dummy behavior are removed without breaking compilation.
  • No public JournalManager remains in the new Journal service API.
  • docs/Terminologi.md defines Service and ServiceManager consistently and contains no contradictory Journal terminology.
  • Nenjim documentation reflects the implemented model and the JournalService/JournalServiceManager responsibility split.
  • Unrelated services remain unchanged.
  • No Context, resolver, classloader, download or signing behavior is introduced.
  • The OpenSpec change remains active and unarchived for review.
Title: Implement the Nenjim Journal model, text parser, query service and lifecycle manager ## Background Nenjim Journals need to become a stable, executable domain foundation before Context and classloader work continues. The current branch contains preliminary artifact-scoped Journal examples and obsolete Java skeletons. The Journal design has since been refined substantially. This issue must consolidate those decisions into: * A documented Journal format. * Immutable Java domain objects. * A strict text parser and validator. * A filesystem-independent, read-only `JournalService` for ordinary consumers. * A filesystem-facing `JournalServiceManager` for lifecycle and administration. * A strict public API/implementation package boundary. * Query APIs for inspecting current and historical Journal knowledge. The branch may also contain an intermediate implementation of issue #75 under `com.r35157.nenjim.journal`. That package layout and its concrete public `JournalManager`/`JournalService` split are superseded by the Service/ServiceManager architecture defined below. The intermediate implementation must be refactored without compatibility aliases. Parsing is intentionally included. Many Journal invariants only become real contracts when invalid text is rejected rather than merely described in documentation. This issue does **not** implement dependency resolution, Contexts, classloading or artifact downloading. ## Core Journal model ### One Journal per artifact A Journal describes exactly one artifact. API, test and implementation artifacts therefore have separate Journals, for example: ```text com_r35157_nenjim-hubd-api com_r35157_nenjim-hubd-tests com_r35157_nenjim-hubd-impl_ref ``` Artifact roles such as `API`, `IMPLEMENTATION` and `TEST` are not part of the Journal format. There is no `TYPE` field. The artifact coordinate is derived from: ```text <GROUP>-<MODULE>-<ARTIFACT> ``` The complete reference to an artifact release is: ```text <GROUP>-<MODULE>-<ARTIFACT>:<VERSION> ``` Example: ```text com_r35157_nenjim-hubd-api:1.0.3 ``` There is no separate `JOURNAL_ID`. The coordinate in `[METADATA]` identifies the artifact and its Journal chain. ### Journal revisions are complete snapshots Each Journal file is an immutable, complete snapshot of all current knowledge about one artifact and all its known releases. A revision is not a delta. Nothing is inherited implicitly from its predecessor. Once published, a Journal file is never changed. New knowledge is published as a new Journal revision containing the complete resulting worldview. A newer Journal revision may, without publishing a new artifact release: * Change `DESCRIPTION`. * Change `INFORMATION_URI`. * Add or remove known releases. * Correct information about an existing release. * Correct or change the recorded `LICENSE` for an existing release. * Change `DEPENDS_ON`, `EXCLUDES` or `PREFERRED`. * Change `RECOMMENDED`, `DISCOURAGED` or `BLACKLIST`. * Contain exactly the same semantic worldview as its predecessor. A no-op revision is valid. It may differ only in: ```text JOURNAL_VERSION PREVIOUS_JOURNAL_DIGEST ``` Historical queries must return the complete worldview contained in the requested revision. Revisions must never be merged retroactively. ### Journal time versus release time `JOURNAL_VERSION` identifies when a Journal worldview was published. `PUBLISHED_AT` identifies when an artifact release was published. They are separate concepts. Historical knowledge must be determined from the Journal chain, not by filtering the newest snapshot on `PUBLISHED_AT`. Both use UTC timestamps with millisecond precision: ```text uuuuMMddHHmmssSSS'Z' ``` Example: ```text 20260819121530842Z ``` ## Journal chain The first revision in an artifact’s Journal chain omits `PREVIOUS_JOURNAL_DIGEST`. Every later revision must contain: ```text PREVIOUS_JOURNAL_DIGEST=sha256:<digest> ``` The digest is the SHA-256 digest of the predecessor’s complete UTF-8 Journal text exactly as ingested by the reference implementation, including whitespace and line endings. For each artifact coordinate: * There must be exactly one genesis revision. * Every non-genesis revision must reference the current predecessor. * `JOURNAL_VERSION` must be strictly greater than the predecessor’s version. * Forks and multiple chain heads are invalid. * A missing predecessor is invalid. * A predecessor from another artifact coordinate is invalid. * Supplying the same revision with identical text is idempotent. * Supplying different text for an existing coordinate and `JOURNAL_VERSION` is a conflict. The internal ingestion operation may require predecessors to have been supplied first. `JournalServiceManagerImpl` must therefore load files chronologically within each artifact directory. ## Journal format version 1 A complete non-genesis Journal may look like this: ```text FORMAT_VERSION=1 JOURNAL_VERSION=20260819121530842Z PREVIOUS_JOURNAL_DIGEST=sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa [METADATA] GROUP=com_r35157_nenjim MODULE=hubd ARTIFACT=impl_ref DESCRIPTION='The reference implementation of the Nenjim Hub.' INFORMATION_URI='ftp://ftp.domain.com/components/123.txt' [POLICY] RECOMMENDED=1.0.3:'Best tested release' DISCOURAGED=[1.0.0-->1.0.2],1.0.7:'Known performance degradation' BLACKLIST=1.0.5:'Security issue CVE-1234' [RELEASE 1.0.3] LICENSE='Public Domain' PUBLISHED_AT=20260815193000000Z CONTENT_DIGEST=sha256:123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0 CONTENT_DIGEST=cid1:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a CONTENT_SIZE=234567 DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0--> EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=[1.0.4-->1.0.5]:'Known incompatibility' PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3 ``` ### General parsing rules * The input is UTF-8 text. * `FORMAT_VERSION=1` must be the first actual configuration entry. * Blank lines and full-line comments beginning with `#` may precede it. * Blank lines and full-line comments are otherwise allowed throughout the file. * Full-line and trailing comments beginning with `#` are allowed. * A `#` inside a single-quoted value is part of the value and does not begin a comment. * Unknown, malformed, duplicate or misplaced sections and fields must fail parsing. * An unsupported format version must fail explicitly. * Empty required values must fail. * Unknown fields must never be silently ignored. * Parsing errors must identify the input source when known, the line number and the reason. * The parser must validate the entire Journal before the reference implementation mutates its internal state. * Returned domain objects and collections must be immutable and defensively copied. ### Required top-level structure The logical section order is: ```text FORMAT_VERSION JOURNAL_VERSION optional PREVIOUS_JOURNAL_DIGEST [METADATA] optional [POLICY] one or more [RELEASE <version>] sections ``` Required `[METADATA]` fields: ```text GROUP MODULE ARTIFACT DESCRIPTION ``` `DESCRIPTION` applies to the artifact and may change in a later Journal revision. `DESCRIPTION` is required exactly once and its value must be enclosed in single quotes: ```text DESCRIPTION='The public API for Nenjim Hub.' ``` The parser stores the decoded text value without the surrounding quotes. `[METADATA]` may additionally contain one optional `INFORMATION_URI`: ```text INFORMATION_URI='ftp://ftp.domain.com/components/123.txt' ``` `INFORMATION_URI` is the primary address at which a person or tool can find more information about the artifact or the project/module to which it belongs. Related artifact Journals may therefore repeat the same URI. Rules for `INFORMATION_URI`: * It is optional and may occur at most once. * Its value must be enclosed in single quotes. * Its decoded value must be a syntactically valid, non-empty absolute URI. * Format version 1 does not restrict the URI scheme. Values such as `https:`, `ftp:` and `ipfs:` are allowed. * The Journal model must store it as an optional `java.net.URI`, not merely as an unvalidated string. * The parser stores only the raw URI represented by the quoted value. Markdown link syntax such as `[text](URI)` is not part of the Journal format. * Nenjim must not automatically open, fetch or otherwise interpret the referenced content in this issue. * A later Journal revision may change or remove the URI without publishing a new artifact release. The following obsolete concepts must be rejected: ```text JOURNAL_ID TYPE ``` ## Artifact release versions Every `[RELEASE ...]` heading contains one exact Semantic Version: ```text major.minor.patch ``` Examples: ```text 0.1.0 1.0.3 12.45.600 ``` Release versions must contain all three numeric components. The following are not artifact release versions: ```text 1 1.0 ``` Prereleases and build metadata are intentionally unsupported in format version 1: ```text 1.0.0-beta 1.0.0-rc.1 1.0.0+build.42 ``` Release versions must be unique within a Journal snapshot. The existing `SemanticVersion` type may be reused for exact releases, but the Journal parser must never normalize an incomplete `1.0` release into `1.0.0`. ## Release content Each release contains: ```text LICENSE PUBLISHED_AT one or more CONTENT_DIGEST entries CONTENT_SIZE ``` `LICENSE` records the license under which that particular artifact release is made available: ```text [RELEASE 0.1.0] LICENSE='Public Domain' PUBLISHED_AT=20260815193000000Z CONTENT_DIGEST=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef CONTENT_DIGEST=cid1:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a CONTENT_SIZE=123456 ``` Rules for `LICENSE`: * Every release must contain exactly one `LICENSE` field. * Its value must be enclosed in single quotes. * The parser must reject a missing, duplicate, malformed or empty `LICENSE` value. * Format version 1 treats the decoded value as unrestricted text and stores it in the `ArtifactRelease` model. * The parser must not require a known license identifier, normalize the value or interpret its legal meaning. * Different releases in the same Journal may use different license values. * Future Nenjim functionality may filter releases by license, but license filtering is outside this issue. Supported content digest schemes in format version 1 are: ```text sha256 cid1 ``` Rules: * At least one supported content digest is required. * A release may contain both SHA-256 and CIDv1 digests. * There may be at most one digest of each type. * Digest syntax must be validated. * A CIDv1 value must use the exact canonical unpadded lowercase Base32 spelling of its decoded bytes. * The CID version, multicodec, multihash code and digest-length fields must use their shortest unsigned varint encodings within the supported non-negative `long` range. * Non-zero unused Base32 padding bits, non-minimal varints, overflow, unterminated varints and a digest length that does not match the payload must fail. * Unknown digest types must fail. * `CONTENT_SIZE` is the artifact size in bytes and must be a valid non-negative `long`. * Content retrieval, download and digest verification of downloaded artifacts are outside this issue. * Different Journal revisions may repeat the same release and content information. * Separate artifact releases are allowed to identify identical content bytes. `CONTENT_DIGEST` replaces the earlier idea of `CONTENT_TYPE`. Content providers will later decide how the bytes are retrieved. ## Version-expression model Artifact versions and version expressions are different domain concepts: ```text SemanticVersion = one exact artifact release VersionExpression = a set of acceptable artifact releases ``` A version-expression endpoint must contain at least: ```text major.minor ``` A `major`-only expression is invalid. ### Exact versions These expressions are equivalent and must produce the same Java representation: ```text 1.0.3 [1.0.3] ``` A bare exact value must always contain `major.minor.patch`. ### Minor series ```text [1.0] ``` means every stable `1.0.x` release: ```text >= 1.0.0 and < 1.1.0 ``` It must not be normalized into the exact version `1.0.0`. ### Closed ranges Ranges use `-->`. Square brackets include a boundary and parentheses exclude it. ```text [1.0.3-->1.0.7] ``` includes both endpoints. ```text [1.0.3-->1.0.7) ``` excludes `1.0.7`. When an endpoint contains only `major.minor`, it denotes the whole minor series at that boundary. Therefore: ```text [1.0-->1.2] ``` includes all `1.0.x`, `1.1.x` and `1.2.x` releases. ```text [1.0-->1.2) ``` excludes the entire `1.2.x` series. An explicitly cross-major range is allowed: ```text [1.0-->3.0) ``` It includes major versions 1 and 2 but not major version 3. Crossing a major boundary must always be explicit. ### Open upper ranges An omitted upper boundary stops before the next major version: ```text [1.0--> ``` means: ```text >= 1.0.0 and < 2.0.0 ``` ```text (1.0--> ``` means: ```text >= 1.1.0 and < 2.0.0 ``` ```text [1.0.3--> ``` means: ```text >= 1.0.3 and < 2.0.0 ``` ### Unsupported comparison syntax Mathematical comparison operators are not supported: ```text < <= > >= = ``` An expression such as `>=1.0` is dangerous because its ordinary meaning crosses into potentially incompatible major versions. Only exact selectors and Nenjim ranges are accepted. ### Expression lists Where a field supports several expressions, expressions are separated by commas and represent their union: ```text [1.0.0-->1.0.3],[1.0.7],1.1.1 ``` Whitespace may appear around commas and `-->`, but not inside a numeric version. Empty elements, reversed boundaries and empty ranges must fail. The Java model must normalize semantically equivalent syntax. It does not need to preserve whether an exact version was originally written as `1.0.3` or `[1.0.3]`. ## Dependencies Dependencies belong to individual artifact releases. Every dependency uses an explicit scheme: ```text <scheme>:<scheme-specific-coordinate> ``` Format version 1 implements Nenjim dependencies: ```text nenjim:com_r35157_nenjim-hubd-api ``` The scheme is lowercase. Future schemes may include Maven: ```text maven:org.apache.commons:commons-collections4:4.5.0 ``` Maven dependency handling is outside this issue. Unknown or unsupported schemes must produce a clear error and must never be ignored. ### `DEPENDS_ON` A release may declare several dependencies, but the same dependency target may have exactly one `DEPENDS_ON` entry: ```text DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0--> ``` `DEPENDS_ON` defines the base set of acceptable versions. ### Dependency `EXCLUDES` `EXCLUDES` removes versions from one particular dependency relationship: ```text EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=[1.0.3-->1.0.5] EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=1.0.7:'Known incompatibility' ``` This does not claim that the dependency releases are generally defective. It only states that this artifact release must not be combined with them. Rules: * `EXCLUDES` is a hard constraint. * It requires a corresponding `DEPENDS_ON`. * It may occur multiple times for the same target. * A line may contain several comma-separated version expressions. * Repeated exclusion sets are combined as a union. * The optional description applies to every expression on that line. ### Dependency `PREFERRED` `PREFERRED` identifies a preferred subset of the dependency’s permitted versions: ```text PREFERRED=nenjim:com_r35157_nenjim-hubd-api=[1.0.7] ``` Rules: * `PREFERRED` is a hint, not a hard constraint. * It requires a corresponding `DEPENDS_ON`. * It may occur multiple times for the same target. * Repeated preferred sets are combined as a union. * The complete preferred set must be a subset of: ```text DEPENDS_ON - EXCLUDES ``` A preferred version must never make an otherwise invalid version valid. The Journal layer only represents these rules. It does not select a dependency graph. ## Artifact policy `[POLICY]` describes policy for releases of the artifact described by the current Journal. Policy rules may be changed in later Journal revisions without publishing new artifact content. ### `BLACKLIST` ```text BLACKLIST=1.0.0:'Security issue CVE-1234' BLACKLIST=[1.1.0-->1.1.3]:'Corrupt release contents' ``` `BLACKLIST` is a hard global rule: matching releases must not be selected. It may occur multiple times and may contain comma-separated exact versions or ranges. ### `DISCOURAGED` ```text DISCOURAGED=[1.0.1-->1.0.3],[1.0.7],1.1.1 DISCOURAGED=1.0.7:'Major performance issue' ``` `DISCOURAGED` is a negative hint. Matching releases should be avoided when a better usable choice exists, but they remain selectable. It may occur multiple times and may contain comma-separated exact versions or ranges. ### `RECOMMENDED` ```text RECOMMENDED=1.0.3:'Best tested release' ``` `RECOMMENDED` is a positive hint identifying a particularly well-tested release. Rules: * Each entry identifies one exact release. * `1.0.3` and `[1.0.3]` are equivalent. * Ranges are not allowed. * The identified release must exist in the same Journal snapshot. * The field may be repeated, allowing separate supported major lines to have their own recommended release. * It is a hint, not a hard eligibility rule. For example: ```text 1.0.2 1.0.3 RECOMMENDED 1.0.4 ``` A resolver should normally prefer `1.0.3`. If it cannot or does not want to use that release—for example because it is offline and only has `1.0.4` locally—it may automatically select the highest otherwise valid release, here `1.0.4`. When `1.0.3` later becomes available, the Hub may prefer it even though changing from `1.0.4` to `1.0.3` is numerically a downgrade. The desired long-term state is a valid graph containing as many compatible recommended components as possible. Highest version is a fallback, not always the primary goal. The actual resolution, reconciliation and live upgrade/downgrade behavior belong to later Context and NenjimHub work. ### Policy overlap Overlapping policy rules are valid. This permits a broad range to be described without splitting it merely because one release inside it has a stronger policy: ```text DISCOURAGED=[1.0-->1.1] BLACKLIST=1.0.7:'Security vulnerability' ``` Hard rules always win over hints: ```text BLACKLIST > DISCOURAGED / RECOMMENDED ``` The Java model must preserve individual policy entries and their descriptions. It must not flatten everything into an unexplained set of versions. ## Optional rule descriptions The following repeatable rules may have an optional description: ```text BLACKLIST DISCOURAGED RECOMMENDED EXCLUDES PREFERRED ``` Syntax: ```text <rule-value>:'Description' ``` The description applies to every expression on the line. Descriptions use single quotes. Format version 1 must support escaping at least: ```text \' embedded single quote \\ embedded backslash ``` A malformed, unterminated or empty explicitly supplied description must fail. The same single-quoted text syntax and escaping rules apply to mandatory metadata `DESCRIPTION`, optional metadata `INFORMATION_URI` and release `LICENSE` values. The Java model stores their decoded contents without the surrounding quotes or escape characters. ## Service terminology and responsibility split Nenjim distinguishes the daily application-facing service API from the lifecycle and administrative API that owns and maintains that service. ### `Service` A `Service` is the public domain API used during ordinary operation by applications and other services. A service interface: * Exposes the domain operations that ordinary consumers need. * Must not expose lifecycle operations such as start or stop. * Must not expose configuration, data-source administration, refresh or mutation of the service’s internal knowledge. * Must not expose implementation helpers, parsers or mutable storage. For Journals, this interface is `JournalService`. ### `ServiceManager` A `ServiceManager` is the lifecycle and administrative API used by NenjimHub or another composition/lifecycle owner. A service-manager interface: * Starts and stops the service. * Owns configuration and reports where the service’s data is located. * Performs explicit refresh and maintenance of the service’s internal knowledge. * Owns and exposes the corresponding daily service interface after successful initialization. * Is not the interface supplied to ordinary consumers for daily domain queries. For Journals, this interface is `JournalServiceManager`. These definitions must be added to `docs/Terminologi.md` and used consistently throughout the Nenjim documentation. This issue applies the terminology only to the Journal subsystem and must not change unrelated services. ## Java packages and public API boundary The public Journal service package is: ```text com.r35157.nenjim.service.journal ``` Only the two central public interfaces live directly in that package: ```text JournalService JournalServiceManager ``` Public immutable Journal model objects live under: ```text com.r35157.nenjim.service.journal.model ``` This package contains at least: ```text Journal JournalMetadata JournalPolicy ArtifactRelease ArtifactDependency PolicyRule ``` Public value types live under: ```text com.r35157.nenjim.service.journal.valuetypes ``` This package contains at least: ```text ArtifactCoordinate JournalVersion ContentDigest DependencyTarget VersionExpression VersionExpressionSet ``` Public Journal-specific exceptions live under: ```text com.r35157.nenjim.service.journal.exception ``` This package contains at least: ```text InvalidJournalException ``` The reference implementation lives under: ```text com.r35157.nenjim.service.journal.impl.ref ``` It contains at least: ```text JournalServiceImpl JournalServiceManagerImpl JournalTextParser ``` Any parser contract, mutable ingestion API, chain store, filesystem loader or other implementation helper must remain in the reference-implementation package or a subpackage. Such implementation types must not leak into public service or model signatures. Requirements for the public model: * Use immutable records/classes where appropriate. * Use `Instant` or semantically specific immutable value objects instead of `java.util.Date`. * Do not return `null` for missing query results. * Use immutable collections and defensive copies. * Preserve separate policy rules and descriptions. * Version expressions must support membership and set-containment operations required for validation. * Exact artifact versions and partial version boundaries must remain different types or otherwise retain their different semantics. * `Journal` or its metadata model must expose `INFORMATION_URI` as an optional `java.net.URI`. * `ArtifactRelease` must expose the decoded `LICENSE` text exactly as represented semantically by the Journal; it must not use an enum or a fixed list of licenses. * `Journal` represents one complete parsed revision. The fact that many public model types are Java records does not make all of them value types. Complete domain snapshots and composed domain structures belong in `model`; small types defined completely by their value belong in `valuetypes`. ## Validation ownership Validation is deliberately distributed by responsibility: * `JournalTextParser` validates textual syntax, section and field ordering, quoting, comments, duplicates, source/line diagnostics and conversion from text. * Public model records/classes and value types validate their own construction invariants so invalid public domain objects cannot be created by bypassing the parser. * `JournalServiceImpl` validates cross-revision chain relationships and maintains the immutable in-memory Journal knowledge exposed through `JournalService`. * `JournalServiceManagerImpl` validates filesystem placement and naming and controls lifecycle, loading and refresh. Complex constructor validation that protects a public model invariant may remain with the public record/class. Parsing algorithms and filesystem-specific rules must remain implementation details. ## `JournalService` `JournalService` is the filesystem-independent, read-only API used by ordinary consumers to query the Journal knowledge currently maintained by the service manager. It must provide operations sufficient to: * List known artifact coordinates. * List all revisions for an artifact in chronological order. * Obtain the latest revision for an artifact. * Obtain an exact revision by artifact coordinate and `JournalVersion`. * Obtain the newest revision at or before a supplied `JournalVersion`. * Obtain an exact artifact release from a selected revision. * Inspect the optional information URI through the immutable Journal metadata model. * Inspect the license recorded for an artifact release through the immutable release model. * Inspect dependencies and policy through the immutable model. * Distinguish “not found” without returning `null`. `JournalService` must not expose: * `start`, `stop` or lifecycle state changes. * Filesystem paths or data-source configuration. * Refresh/reload operations. * `update(String)` or any other way for ordinary consumers to mutate Journal knowledge. * Parsers or implementation state. The service does not resolve dependencies or select releases. ## `JournalServiceManager` `JournalServiceManager` is the lifecycle and administrative interface for the Journal service. It must support operations sufficient to: * Start the service and perform its initial Journal loading. * Stop service-owned maintenance cleanly. * Report the configured Journal data root as a normalized `java.nio.file.Path`. * Expose the corresponding `JournalService` for ordinary read-only use. * Perform an explicit refresh/reload. The manager must not duplicate the Journal query API. Ordinary consumers receive `JournalService`, not `JournalServiceManager`. `JournalServiceManagerImpl` is the filesystem-facing reference implementation. It owns a `JournalServiceImpl` and is responsible for: 1. Finding Journal files on disk. 2. Reading each complete file as UTF-8 text. 3. Validating file placement and naming. 4. Sorting each artifact’s revisions chronologically. 5. Supplying each complete textual Journal to an internal, non-public ingestion operation on the reference implementation. 6. Making the resulting knowledge available through the owned `JournalService`. The initial start must fail clearly if the configured root or Journal data is invalid. The manager must not report a successful start until the initial loading operation has completed successfully. Stopping must not delete, rewrite or otherwise mutate Journal files. The reference implementation’s internal ingestion operation must: 1. Reject null, empty or blank input. 2. Parse the complete text. 3. Validate all structural and semantic invariants. 4. Calculate the Journal’s SHA-256 digest from the exact UTF-8 text. 5. Validate the predecessor relationship. 6. Update the in-memory state atomically for that Journal revision. 7. Make the resulting immutable `Journal` immediately queryable through `JournalService`. A failed ingestion must leave the previously accepted in-memory state unchanged for that operation. Supplying identical text again is idempotent; supplying conflicting text for the same coordinate and Journal version must fail. The reference implementation’s in-memory Journal state and ingestion logic must remain filesystem-independent. Filesystem access belongs only to `JournalServiceManagerImpl`. Repeated refreshes must be idempotent for unchanged files and must discover newly added revisions. Filesystem watching, background polling and remote synchronization are future `ServiceManager` responsibilities but remain outside this issue. ## Filesystem layout Runtime Journals use: ```text data/nenjim/journals/ └── <artifact-coordinate>/ ├── <journal-version>.journal └── <newer-journal-version>.journal ``` Example: ```text data/nenjim/journals/ └── com_r35157_nenjim-hubd-api/ ├── 20260816201156858Z.journal └── 20260819121530842Z.journal ``` Version-controlled examples use: ```text <journal-version>.journal.example ``` Example: ```text data/nenjim/journals/com_r35157_nenjim-hubd-api/20260816201156858Z.journal.example ``` Rules: * Directory name must equal the coordinate derived from `[METADATA]`. * Runtime filename without `.journal` must equal `JOURNAL_VERSION`. * Existing files are immutable and must never be overwritten by `JournalServiceManagerImpl`. * `.journal.example` files are documentation/examples and must not be loaded as runtime Journals. * `JournalServiceManagerImpl` reads only the defined artifact-directory/revision-file structure. * A missing or unreadable configured Journal root must fail clearly; the manager implementation must not create it silently. * File downloading, remote synchronization and deletion are outside this issue. Move and update the current flat example files to this structure. At least one example should demonstrate: * Single-quoted metadata `DESCRIPTION`. * An optional single-quoted `INFORMATION_URI`, including a valid non-HTTP scheme. * A single-quoted `LICENSE` for every release. * Dependencies. * Dependency exclusions and preferences. * Policy. * Exact and range syntax. * Optional descriptions. If examples include multiple revisions in one chain, their `PREVIOUS_JOURNAL_DIGEST` values must be real digests of the predecessor example text. ## Refactor of the intermediate issue #75 implementation If the current branch already contains the first issue #75 implementation under: ```text com.r35157.nenjim.journal com.r35157.nenjim.journal.impl.ref ``` it must be migrated completely to the package and responsibility structure defined in this issue. In particular: * Move the current public `JournalManager` query operations to the new public read-only `JournalService` interface; no public `JournalManager` remains. * Put the filesystem-independent in-memory query and chain-state implementation behind `JournalServiceImpl`. * Replace the current concrete filesystem-facing `JournalService` with the public lifecycle/administrative `JournalServiceManager` interface and the reference `JournalServiceManagerImpl`. * Keep mutable ingestion and chain-state operations internal to the reference implementation. * Move public domain models, value types and exceptions into their specified subpackages. * Move parser contracts and implementations out of the public API packages. * Update all imports, documentation and OpenSpec artifacts. * Do not retain deprecated aliases, forwarding wrappers or compatibility types in the superseded packages. ## Legacy cleanup Remove the obsolete Journal/module skeletons when their references have been removed or updated: ```text com.r35157.nenjim.hubd.journal.Journal com.r35157.nenjim.hubd.journal.JournalManager com.r35157.nenjim.hubd.impl.ref.JournalManagerImpl com.r35157.nenjim.hubd.module.Module com.r35157.nenjim.hubd.module.Release com.r35157.nenjim.hubd.module.Dependency com.r35157.nenjim.valuetypes.journal.JournalId ``` Remove the hardcoded dummy `getRelease(...)` behavior. Update obsolete imports and dead references so the project compiles, but do not integrate the new Journal service into Context or classloading in this issue. ## Documentation Update at least: ```text docs/Nenjim.md docs/Terminologi.md docs/Nenjim-public.html ``` The documentation must describe: * One Journal per artifact. * Artifact coordinates. * Optional scheme-independent `INFORMATION_URI` metadata. * Complete immutable snapshots. * Journal chains and historical worldview. * Journal revision time versus artifact publication time. * Content digests. * Per-release license text and its future role as filterable metadata. * Version-expression semantics. * Dependency schemes. * `DEPENDS_ON`, dependency `EXCLUDES` and `PREFERRED`. * `[POLICY]`, `BLACKLIST`, `DISCOURAGED` and `RECOMMENDED`. * The distinction between hard constraints and hints. * Recommended-version fallback and possible later convergence/downgrade. * Filesystem layout. * The general distinction between a daily application-facing `Service` and its lifecycle/administrative `ServiceManager`. * `JournalService` as the read-only daily query API. * `JournalServiceManager` as the lifecycle, data-location and refresh API. * The separation between public `model`, `valuetypes` and `exception` packages and the reference implementation. * Which parts are implemented now and which remain future vision. Remove or rewrite the currently obsolete documentation claiming that: * One Journal contains several artifacts. * `JournalId` is stored explicitly. * Artifact roles are stored as types. * A resolver always chooses the numerically highest version. ## OpenSpec Create an OpenSpec change for issue #75 covering: * Journal domain model. * Journal text format version 1. * Parsing and modelling of single-quoted `DESCRIPTION`, optional `INFORMATION_URI` and `LICENSE` values. * Parser and validation behavior. * Journal-chain management. * Filesystem-independent, read-only `JournalService`. * Filesystem-backed lifecycle and administrative `JournalServiceManager`. * Internal non-public parsing, ingestion and chain-state behavior. * Public API, model, value-type, exception and reference-implementation package boundaries. * Service and ServiceManager terminology in `docs/Terminologi.md`. * Query behavior. * Documentation and example migration. * Legacy cleanup. The OpenSpec scenarios must include valid and invalid examples for the important parsing and validation rules, even though automated tests are currently deferred. Leave the change active for review. Do not sync or archive it as part of implementation. ## Out of scope This issue must not implement: * Context or resolved Context objects. * Dependency-graph resolution. * Automatic release selection. * Runtime reconciliation, upgrade or downgrade. * Classloader behavior. * Artifact downloading. * IPFS retrieval. * Maven dependency resolution. * Signature generation or validation. * Journal publishing. * Remote Journal synchronization. * Filesystem watching or background polling. * UI or CLI management. * Opening, downloading or interpreting `INFORMATION_URI` content and scheme-specific URI behavior. * License-name normalization, license compatibility decisions or filtering releases by license. * Changes to unrelated services. * Wiring `JournalServiceManager` into Context, classloading or NenjimHub startup orchestration. * Automated unit or integration tests; these remain deferred until the planned Nenjim testing work. ## Acceptance criteria * The latest project compiles successfully. * The only types directly in `com.r35157.nenjim.service.journal` are the public `JournalService` and `JournalServiceManager` interfaces. * Public composed Journal domain objects are under `com.r35157.nenjim.service.journal.model`. * Public Journal value types are under `com.r35157.nenjim.service.journal.valuetypes`. * Public Journal-specific exceptions are under `com.r35157.nenjim.service.journal.exception`. * Reference implementation, parser, mutable ingestion and filesystem logic are under `com.r35157.nenjim.service.journal.impl.ref` or its subpackages and do not leak into public signatures. * The model represents every field and rule defined above without loss of meaning. * Metadata `DESCRIPTION` is required in single quotes, is decoded by the parser and is available from the model. * Metadata `INFORMATION_URI` is optional, single-quoted, validated as an absolute URI and exposed as `Optional<URI>` without restricting its scheme. * Every artifact release has one required single-quoted `LICENSE` value which is decoded and available from `ArtifactRelease` without vocabulary validation or normalization. * CIDv1 content identities require canonical unpadded lowercase Base32, shortest non-overflowing unsigned varints and a matching digest payload length. * Invalid format versions, timestamps, versions, ranges, digests, policies, dependencies and chain relationships fail clearly. * `JournalService` is a filesystem-independent, read-only query interface with no lifecycle, data-location, refresh or mutation operations. * `JournalServiceManager` is the lifecycle and administrative interface for start, stop, data location, explicit refresh and access to the corresponding `JournalService`. * `JournalServiceImpl` maintains the in-memory Journal chains and serves current and historical queries without exposing public mutation. * `JournalServiceManagerImpl` reads the documented disk layout chronologically and supplies complete strings to the internal ingestion operation. * Internal ingestion parses, validates and atomically stores each complete Journal snapshot; failed ingestion leaves previously accepted state unchanged. * Initial manager start fails clearly for an invalid root or invalid Journal data and is not reported as successfully started before initial loading completes. * Journal queries support current and historical revisions. * Repeated identical updates are idempotent. * Conflicting revisions and chain forks fail. * Policy rules and their descriptions remain individually inspectable. * Current example Journals are moved to the revision-directory layout and updated to format version 1. * Obsolete Journal/module skeletons and dummy behavior are removed without breaking compilation. * No public `JournalManager` remains in the new Journal service API. * `docs/Terminologi.md` defines `Service` and `ServiceManager` consistently and contains no contradictory Journal terminology. * Nenjim documentation reflects the implemented model and the `JournalService`/`JournalServiceManager` responsibility split. * Unrelated services remain unchanged. * No Context, resolver, classloader, download or signing behavior is introduced. * The OpenSpec change remains active and unarchived for review.
minimons added the enhancement label 2026-08-19 09:33:45 +02:00
minimons self-assigned this 2026-08-19 09:33:45 +02:00
minimons added this to the AssetAZ project 2026-08-19 09:33:45 +02:00
minimons changed title from Implement the Nenjim Journal model, text parser, manager and filesystem service to Implement the Nenjim Journal model, text parser, query service and lifecycle manager 2026-08-20 11:03:42 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#75