= core-events

== purpose

Foundation layer that defines the event schema, serialization infrastructure, identity primitives, and projection framework used by every other module in the system. All state is rebuilt from events, and this module provides the tools to define, store, serialize, and replay them. It also houses shared vocabulary types (SessionId, StageId, etc.) and cross-cutting value types (Tier, ApprovalStatus, RiskLevel) that would cause circular dependencies if defined elsewhere.

== responsibilities

* Defines the `EventPayload` sealed interface — all event types in the system implement it
* Provides `NewEvent` and `StoredEvent` envelopes with metadata (eventId, sessionId, timestamp, causationId, correlationId)
* Declares all concrete event types: session events, stage events, orchestration events, inference events, tool events, artifact events, approval events, context events, risk events
* Defines the `EventStore` interface for append and read operations on the event log
* Implements `EventDispatcher` as a convenience wrapper over `EventStore.append()`
* Defines shared identity types (`SessionId`, `EventId`, `StageId`, `TransitionId`, `ArtifactId`, etc.) and the underlying `TypeId` value class
* Contains the polymorphic serialization registry (`Serialization.kt`) — every `EventPayload` subclass must be registered here
* Provides `EventSerializer` / `JsonEventSerializer` for encode/decode
* Defines the projection framework (`Projection<S>`, `StateBuilder<S>`, `DefaultStateBuilder`, `EventReplayer<S>`, `DefaultEventReplayer`)
* Shares cross-module value types: `ApprovalOutcome`, `ApprovalStatus`, `GrantScope`, `Tier`, `UserSteering`, `RiskLevel`, `RiskAction`, `RiskSignal`, `RiskSummary`, `ArtifactLifecyclePhase`, `ArtifactRelationshipType`, `RetryPolicy`, `TokenUsage`, `OrchestrationState`, `OrchestrationStatus`

== non-responsibilities

* Does not own any domain logic beyond event definition and serialization
* Does not implement the `EventStore` — that is in `infrastructure` modules (SQLite, etc.)
* Does not define reducers or projectors for domain state (those live in `core:sessions`, `core:kernel`, etc.)
* Does not enforce causal consistency or idempotency — those are the store's or kernel's responsibility
* Does not handle event routing to subscribers — `EventDispatcher` emits but does not fan out

== key types

=== EventPayload
* **kind**: sealed interface
* **purpose**: base type for all domain events. Every event in the system implements this.

=== NewEvent
* **kind**: data class
* **purpose**: event prior to persistence, carries metadata and payload
* **fields**: metadata (EventMetadata), payload (EventPayload)

=== StoredEvent
* **kind**: data class
* **purpose**: event after persistence, adds sequence numbers
* **fields**: metadata, sequence (global sequence), sessionSequence (per-session monotonic), payload

=== EventMetadata
* **kind**: data class
* **purpose**: structured metadata attached to every event
* **fields**: eventId, sessionId, timestamp, schemaVersion, causationId, correlationId

=== EventStore
* **kind**: interface
* **purpose**: append events, read by session sequence, subscribe to live events, enumerate all events

=== EventDispatcher
* **kind**: class
* **purpose**: convenience wrapper that builds EventMetadata and calls EventStore.append

=== TypeId
* **kind**: value class (inline)
* **purpose**: type-safe identifier wrapper around String. Used for all identity types.
* **invariants**: non-blank, no leading/trailing whitespace

=== Projection&lt;S&gt;
* **kind**: interface
* **purpose**: fold function over events: `initial()` returns base state, `apply(state, event)` returns next state

=== StateBuilder&lt;S&gt;
* **kind**: interface
* **purpose**: builds state from a list of StoredEvents

=== DefaultStateBuilder&lt;S&gt;
* **kind**: class
* **purpose**: folds events through a Projection to produce state

=== EventReplayer&lt;S&gt;
* **kind**: interface
* **purpose**: rebuilds state for a given sessionId from the event store

=== DefaultEventReplayer&lt;S&gt;
* **kind**: class
* **purpose**: reads events from EventStore and folds through DefaultStateBuilder

=== EventSerializer
* **kind**: interface
* **purpose**: serialize/deserialize EventPayload to/from String

=== JsonEventSerializer
* **kind**: class
* **purpose**: JSON implementation of EventSerializer using kotlinx.serialization with the polymorphic event module

=== Tier
* **kind**: enum
* **purpose**: five-level trust tier (T0 through T4) for tool execution and approval routing
* **variants**: T0 (level 0, most trusted), T1, T2, T3, T4 (level 4, least trusted)

=== ApprovalStatus
* **kind**: sealed interface
* **variants**: PENDING, COMPLETED, TIMED_OUT

=== ApprovalOutcome
* **kind**: enum
* **variants**: APPROVED, REJECTED, AUTO_APPROVED

=== GrantScope
* **kind**: sealed interface
* **variants**: SESSION (entire session), STAGE(StageId), PROJECT(ProjectId)

=== RiskLevel
* **kind**: enum
* **variants**: LOW, MEDIUM, HIGH, CRITICAL

=== RiskAction
* **kind**: enum
* **variants**: PROCEED, PROMPT_USER, BLOCK

=== RiskSignal
* **kind**: sealed class
* **purpose**: discriminated signals that feed into risk assessment
* **variants**: CycleWithoutExit(cycleId), RepeatedFailure(reason, count), ValidationErrors(errorCount), InferenceTimeout(elapsedMs)

=== OrchestrationState
* **kind**: data class
* **purpose**: projection state for workflow orchestration
* **fields**: workflowId, currentStageId, status, retryCount, pauseReason, pendingApproval, failureReason, retryPolicy

=== OrchestrationStatus
* **kind**: enum
* **variants**: IDLE, RUNNING, PAUSED, COMPLETED, FAILED, CANCELED

=== RetryPolicy
* **kind**: data class
* **purpose**: maxAttempts and backoffMs for execution retry

=== TokenUsage
* **kind**: data class
* **purpose**: promptTokens + completionTokens (totalTokens computed)

=== AnyMapSerializer
* **kind**: object (KSerializer)
* **purpose**: kotlinx.serialization serializer for `Map<String, Any>` used by ToolRequest and ToolReceipt

=== Concrete event types

| Event class | Purpose |
|---|---|
| SessionStartedEvent | Session lifecycle began |
| SessionPausedEvent | Session was paused |
| SessionResumedEvent | Session was resumed |
| SessionCompletedEvent | Session ended normally |
| SessionFailedEvent | Session ended with error |
| StageStartedEvent | Workflow stage execution began |
| StageCompletedEvent | Workflow stage completed successfully |
| StageFailedEvent | Workflow stage failed |
| TransitionExecutedEvent | Transition between stages occurred |
| WorkflowStartedEvent | Entire workflow began |
| WorkflowCompletedEvent | Entire workflow completed |
| WorkflowFailedEvent | Entire workflow failed |
| OrchestrationPausedEvent | Orchestration blocked (approval, user request) |
| OrchestrationResumedEvent | Orchestration unblocked |
| RetryAttemptedEvent | Stage execution retry occurred |
| InferenceStartedEvent | LLM inference began |
| InferenceCompletedEvent | LLM inference completed with response |
| InferenceFailedEvent | LLM inference failed |
| InferenceTimeoutEvent | LLM inference timed out |
| ModelLoadedEvent | Model loaded into memory |
| ModelUnloadedEvent | Model unloaded from memory |
| ToolInvocationRequestedEvent | Tool call was requested by LLM |
| ToolExecutionStartedEvent | Tool execution began |
| ToolExecutionCompletedEvent | Tool execution completed with receipt |
| ToolExecutionFailedEvent | Tool execution failed |
| ToolExecutionRejectedEvent | Tool execution rejected by policy/approval |
| ToolInvokedEvent | Legacy tool invocation event |
| ApprovalRequestedEvent | Approval was requested |
| ApprovalDecisionResolvedEvent | Approval decision was made |
| ApprovalGrantCreatedEvent | Approval grant was created |
| ApprovalGrantExpiredEvent | Approval grant expired |
| ContextBuildingStartedEvent | Context pack construction began |
| ContextPackBuiltEvent | Context pack was built |
| CompressionAppliedEvent | Context compression was applied |
| LayerTruncatedEvent | Context layer was truncated |
| ContextBuildingFailedEvent | Context pack construction failed |
| ContextBuildingInterruptedEvent | Context building interrupted during replay |
| ArtifactCreatedEvent | Artifact was created |
| ArtifactValidatingEvent | Artifact validation began |
| ArtifactValidatedEvent | Artifact validation passed |
| ArtifactRejectedEvent | Artifact validation failed |
| ArtifactSupersededEvent | Artifact was superseded by another |
| ArtifactArchivedEvent | Artifact was archived |
| ArtifactRelationshipAddedEvent | Relationship between artifacts was recorded |
| SteeringNoteAddedEvent | User steering note was added |
| RiskAssessedEvent | Risk assessment was performed |

== event flow

*inbound:*
Events enter this module via `EventDispatcher.emit()` or `EventStore.append()`. Callers construct a `NewEvent` (or provide an `EventPayload` which the dispatcher wraps) and the store persists it.

*outbound:*
Events leave this module through `EventStore.read()`, `readFrom()`, `allEvents()`, or `subscribe()`/`subscribeAll()`. The serialization layer (`JsonEventSerializer`) converts `EventPayload` to/from JSON strings for storage or transport.

This module does not produce or consume events at a domain level — it is the plumbing through which all other modules' events flow.

== integration points

* `:core:sessions` — uses `EventStore`, `EventReplayer`, `Projection`, `StoredEvent`, session identity types, and `DefaultStateBuilder`
* `:core:kernel` — uses `EventStore`, `EventDispatcher`, `EventMetadata`, all event types, identity types, `Projection`, `EventReplayer`
* `:core:transitions` — uses identity types (`SessionId`, `StageId`, `TransitionId`, `ArtifactId`), `StoredEvent`
* `:core:inference` — uses identity types, `TokenUsage`, `EventStore`
* `:core:approvals` — uses identity types, `ApprovalOutcome`, `ApprovalStatus`, `GrantScope`, `Tier`, `UserSteering`
* `:core:validation` — uses identity types
* `:core:tools` — uses identity types, `Tier`, `ToolReceipt`, `ToolRequest`
* `:core:context` — uses identity types (`ContextPackId`, `ContextEntryId`)
* `:core:risk` — uses `RiskLevel`, `RiskAction`, `RiskSignal`, `RiskSummary`
* `:core:artifacts` — uses identity types, `StoredEvent`
* all `:infrastructure:*` modules — implement `EventStore`, use `StoredEvent`, `NewEvent`, `EventSerializer`

== invariants

* Every `EventPayload` subclass must be registered in the polymorphic block in `Serialization.kt`. Missing registration = silent runtime deserialization failure.
* `TypeId` values must be non-blank with no leading/trailing whitespace.
* `StoredEvent.sequence` must be globally monotonic; `sessionSequence` must be monotonic per session.
* Event identity is by `eventId` — stores must enforce idempotency by `eventId`.
* The projection infrastructure must be deterministic: the same events fed in the same order must produce the same state.

== PlantUML diagram

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




== known issues

* `ToolInvokedEvent` (legacy stub event) coexists with the newer `ToolInvocationRequestedEvent` / `ToolExecutionStartedEvent` / `ToolExecutionCompletedEvent` chain. Both are registered in the polymorphic block.
* `RiskSummaryId.kt` is an empty file — the type alias is defined in `IdentityTypes.kt` instead.
* `OrchestrationState` and `OrchestrationStatus` are duplicated across `core:events` and `core:kernel` as separate copies with different packages. The `core:events` copies are the canonical serialized form; the `core:kernel` copies are empty stubs.

== open questions

* Should `StoredEvent` sequence numbers be assigned by the store or generated by the caller? Current interface suggests the store assigns them.
* `Projection` and `EventReplayer` live in the `core:events` module under the `com.correx.core.sessions.projections` package — this is a naming mismatch that may cause confusion.
