= core-artifacts

== purpose

Defines the artifact model — a structured output produced during a workflow stage. Manages artifact lifecycle phases (created → validating → validated/rejected → superseded/archived) as event-sourced state, provides a kind system for typed artifact payloads (file_written, process_result), and supports materialization of file artifacts to disk.

== responsibilities

* Defining the `Artifact` sealed interface and its common fields (id, session, stage, lifecycle phase, lineage, metadata)
* Managing artifact lifecycle phase transitions with state machine validation
* Tracking artifact lineage (parent IDs, relationships, validation report IDs)
* Defining and registering `ArtifactKind` implementations for typed payloads
* Providing JSON schema derivation for each artifact kind
* Materializing file artifacts to disk via `MaterializingArtifactWriter`
* Providing a repository interface for querying artifact state by session, stage, or ID

== non-responsibilities

* Does not manage binary blob storage — that is delegated to `:core:artifacts-store` via `ArtifactStore`
* Does not validate artifact payloads — validation is handled by `:core:validation` via `ArtifactPayloadValidator`
* Does not produce artifacts — they are created by the orchestrator or infrastructure adapters
* Does not manage the full artifact content — only metadata and lifecycle state are projected

== key types

=== Artifact
* **kind**: sealed interface
* **purpose**: Base type for all artifacts. Declares common fields.
* **fields**: `id`, `sessionId`, `stageId`, `schemaVersion`, `createdAt`, `lifecyclePhase`, `lineage`, `metadata`

=== ArtifactState
* **kind**: data class
* **purpose**: Event-sourced projection of a single artifact's lifecycle state.
* **fields**: `phase` (ArtifactLifecyclePhase), `lineage` (ArtifactLineage)

=== ArtifactReducer / DefaultArtifactReducer
* **kind**: interface / class
* **purpose**: Transforms `ArtifactState` given a stored event. Validates lifecycle phase transitions and returns `Result.Failure` for illegal transitions. Handles `ArtifactCreatedEvent`, `ArtifactValidatingEvent`, `ArtifactValidatedEvent`, `ArtifactRejectedEvent`, `ArtifactSupersededEvent`, `ArtifactArchivedEvent`, `ArtifactRelationshipAddedEvent`.

=== ArtifactProjector
* **kind**: class
* **purpose**: Wraps `ArtifactReducer` as a `Projection<ArtifactState>`. Invalid lifecycle transitions are logged as warnings and do not crash replay.

=== ArtifactLineage
* **kind**: data class
* **purpose**: Tracks an artifact's provenance.
* **fields**: `parentIds`, `relationships` (list of ArtifactRelationship), `validationReportIds`

=== ArtifactRelationship
* **kind**: data class
* **purpose**: A directed, typed relationship between two artifacts.
* **fields**: `sourceId`, `targetId`, `type` (ArtifactRelationshipType)

=== ArtifactKind
* **kind**: interface
* **purpose**: Declares a type of artifact with a payload serializer and JSON schema.
* **fields**: `id`, `payloadSerializer`, `llmEmitted`

=== ArtifactKindRegistry / DefaultArtifactKindRegistry
* **kind**: interface / class
* **purpose**: Maps artifact kind IDs to `ArtifactKind` instances. Pre-registers `FileWrittenKind` and `ProcessResultKind`.

=== FileWrittenKind
* **kind**: object
* **purpose**: Artifact kind for files written to disk. Declares a JSON schema with `path`, `content`, `mode`.

=== FileWrittenPayload
* **kind**: data class
* **purpose**: Input payload for a file write operation (before materialization).
* **fields**: `path`, `content`, `mode`

=== FileWrittenArtifact
* **kind**: data class
* **purpose**: Canonical form of a written file after materialization (content-addressed by hash).
* **fields**: `path`, `contentHash`, `size`, `mode`

=== ProcessResultKind
* **kind**: object
* **purpose**: Artifact kind for process execution results. Declares a JSON schema with `command`, `exitCode`, `stdout`, `stderr`, `durationMs`.

=== ProcessResultArtifact
* **kind**: data class
* **purpose**: Record of a process execution.
* **fields**: `command`, `exitCode`, `stdout`, `stderr`, `durationMs`

=== TypedArtifactSlot
* **kind**: data class
* **purpose**: Associates a named artifact slot with its expected kind.
* **fields**: `name` (ArtifactId), `kind` (ArtifactKind)

=== MaterializingArtifactWriter
* **kind**: interface
* **purpose**: Writes a `FileWrittenPayload` to disk within a sandbox root and returns a `FileWrittenArtifact`. Supports content-addressed storage by returning a hash.

=== MaterializationResult
* **kind**: sealed interface
* **purpose**: Outcome of a materialization attempt.
* **variants**: `Success(artifact, resolvedPath)`, `Failure(reason)`

=== ArtifactRepository
* **kind**: interface
* **purpose**: Queries artifact state by session, stage, or ID.

=== JsonSchema / JsonSchemaProperty
* **kind**: data classes
* **purpose**: JSON Schema representation for artifact kind derived schemas.

== event flow

*inbound:*
* `ArtifactCreatedEvent` → sets phase to `CREATED`
* `ArtifactValidatingEvent` → transitions from `CREATED` to `VALIDATING` (fails if phase is not `CREATED`)
* `ArtifactValidatedEvent` → transitions from `VALIDATING` to `VALIDATED`
* `ArtifactRejectedEvent` → transitions from `VALIDATING` to `REJECTED`
* `ArtifactSupersededEvent` → transitions from `VALIDATED` to `SUPERSEDED`
* `ArtifactArchivedEvent` → transitions from `VALIDATED` or `REJECTED` to `ARCHIVED`
* `ArtifactRelationshipAddedEvent` → adds a relationship to the lineage

*outbound:*
* None. This module is purely a projection.

== integration points

* `:core:events` — `StoredEvent`, artifact lifecycle event types, `ArtifactId`, `ArtifactLifecyclePhase`, `ArtifactRelationshipType`

== invariants

* Lifecycle phase transitions follow a strict state machine: `CREATED → VALIDATING → {VALIDATED, REJECTED}`, then `VALIDATED → {SUPERSEDED, ARCHIVED}`, `REJECTED → ARCHIVED`
* Invalid transitions return `Result.Failure` from the reducer — they do not crash the projector
* `ArtifactProjector` catches `IllegalStateException` from invalid transitions, logs a warning, and leaves state unchanged during replay
* `ArtifactKind` registrations are idempotent — the registry is a map, so re-registering overwrites the previous entry

== PlantUML diagram

[plantuml, core-artifacts, "png"]
----
include::../../diagrams/core-artifacts.puml[]
----




== known issues

* `Artifact` sealed interface has no concrete subclasses in the current codebase. The `artifactModule` serializers module has a polymorphic block for `Artifact::class` with no subclass registrations. Concrete artifact types are needed before polymorphic serialization works.
* `ArtifactState` is a single-artifact projection — each `ArtifactState` tracks one artifact's lifecycle. The `ArtifactRepository` interface returns maps keyed by `ArtifactId`, which suggests the caller is responsible for aggregating multiple states.

== open questions

* How are multiple artifact states (one per artifact) stored and queried? The repository returns maps, but the event replayer typically rebuilds a single state per session. The bridge between them is not visible in this module.
* What concrete `Artifact` subclasses are planned? (The sealed interface exists but has no implementations.)
