epic-12: after epic audit and init commit

This commit is contained in:
2026-05-16 11:42:00 +04:00
commit c77277af0b
461 changed files with 28958 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
# Epic 1 — Event System (Append-Only Event Backbone)
## completed deliverables
### 1. event identity and metadata model
implemented a strict identity and causality model for all events in the system.
final structures:
* `EventId`
* `SessionId`
* `CorrelationId`
* `CausationId`
* `EventMetadata`
key properties:
* immutable identifiers
* replay-safe metadata
* explicit causality tracking (no implicit execution order semantics outside sequence)
metadata is strictly separated from payload.
---
### 2. event payload model (domain-agnostic core)
introduced polymorphic event payload system as the root domain abstraction.
final structure:
* `EventPayload` (sealed polymorphic interface)
* domain events:
* `ToolInvokedEvent`
* `ApprovalGrantedEvent`
* `SessionStartedEvent`
* `SessionPausedEvent`
* `SessionResumedEvent`
* `SessionCompletedEvent`
* `SessionFailedEvent`
properties:
* domain-extensible
* serialization-safe via kotlinx.serialization module
* no infrastructure coupling
* no persistence awareness
---
### 3. event envelope model
implemented a strict separation between event metadata and payload.
final structure:
```text id="e1"
StoredEvent
├── metadata (identity + causality + timestamp)
├── sequence (per-session ordering)
└── payload (domain event)
```
envelope guarantees:
* complete event immutability
* replay-safe structure
* strict ordering contract support
* backend-agnostic representation
---
### 4. event store abstraction (core contract)
introduced append-only event store contract as system backbone.
interface guarantees:
* append-only semantics
* deterministic ordering per session
* idempotent writes via `eventId`
* read consistency guarantees
* replay-safe retrieval model
core operations:
```text id="e2"
append(event)
appendAll(events)
read(sessionId)
readFrom(sessionId, sequence)
lastSequence(sessionId)
```
store is the **single source of truth** in the system.
---
### 5. in-memory event store (reference implementation)
implemented deterministic in-memory store for contract validation and testing.
properties:
* thread-safe append operations
* per-session sequencing via atomic counters
* duplicate event protection (eventId-based idempotency)
* deterministic read ordering guarantees
used as baseline correctness oracle for all other stores.
---
### 6. persistence alignment (sqlite implementation foundation)
introduced SQLite-based event store as infrastructure implementation.
responsibilities:
* persistent event storage
* enforcement of append-only constraints at DB level
* deterministic reconstruction of event streams
* compatibility with core event envelope model
ensures parity with in-memory reference implementation via contract tests.
---
### 7. serialization system
implemented deterministic serialization layer for event persistence.
components:
* `JsonEventSerializer`
* `SerializersModule` with polymorphic `EventPayload`
* `eventJson` configuration instance
guarantees:
* stable cross-run serialization
* replay-safe encoding/decoding
* polymorphic payload correctness
* strict schema versioning support
serialization is treated as **infrastructure concern only**, not domain logic.
---
### 8. contract-based enforcement system
introduced contract test framework to enforce event system invariants across implementations.
enforced invariants:
* append-only behavior
* idempotency via eventId
* strict per-session ordering
* deterministic read output
* cross-implementation parity (in-memory vs sqlite)
contract layer ensures:
> correctness is enforced structurally, not assumed per implementation
---
### 9. concurrency and ordering guarantees
validated event store behavior under concurrent access conditions.
guarantees established:
* safe concurrent appends per session
* deterministic sequence assignment
* protection against race-condition-induced ordering violations
* linearized per-session event streams
ensures event log integrity under multi-threaded execution.
---
### 10. replay readiness foundation (implicit but critical outcome)
Epic 1 established the foundational requirement for all higher systems:
> any state must be reconstructable from the event stream alone
achieved via:
* strict envelope model
* deterministic ordering guarantees
* immutable event storage
* idempotent append semantics
this directly enables Epic 2 projection and replay system.
---
# final architecture after Epic 1
```text id="e3"
EventStore (append-only, ordered, idempotent)
StoredEvent (metadata + sequence + payload)
Polymorphic EventPayload system
Serialization layer (kotlinx.serialization)
```
---
# major architectural outcomes
Epic 1 established:
* append-only event-sourced backbone
* strict identity + causality model
* polymorphic domain event system
* deterministic persistence contract
* replay-safe serialization layer
* cross-backend contract enforcement
* concurrency-safe event ordering
---
# what Epic 1 intentionally does NOT include
not implemented:
* projections / state reconstruction
* FSM / session lifecycle logic
* workflow execution engine
* transition system
* kernel orchestration
* runtime execution model
those are explicitly deferred to Epic 2+
---
# final state
Correx now has:
> a deterministic, append-only event backbone with strict identity, causality, and ordering guarantees, fully replay-ready and validated through cross-implementation contract tests, serving as the foundational truth layer for all higher-level system behavior.
+200
View File
@@ -0,0 +1,200 @@
# Epic 1.5 — Store Invariants, Replay Contracts & Projection Enforcement Layer
## completed deliverables
### 1. event store invariants formalization
extended and hardened the EventStore correctness model beyond basic append/read semantics.
formalized invariants:
* append-only enforcement as strict storage rule
* idempotency via `eventId` uniqueness
* monotonic per-session sequencing
* total ordering guarantee per session stream
* deterministic read consistency across implementations
this clarified EventStore as a **strict correctness boundary**, not just a persistence abstraction.
---
### 2. replay contract system (cross-store determinism)
introduced a unified contract layer for verifying replay correctness across all EventStore implementations.
contract guarantees:
* identical event streams MUST produce identical replay outputs
* ordering must be preserved independent of storage backend
* partial replay (cursor-based) must remain deterministic
* replay must be stateless and side-effect free
covered implementations:
* in-memory event store
* sqlite event store
* future persistence adapters
---
### 3. projection contract enforcement layer
defined formal constraints for projection systems to ensure deterministic state reconstruction.
projection invariants:
* projection MUST be a pure function: `EventStream → State`
* projections MUST NOT retain or mutate internal state
* projections MUST NOT depend on external systems
* identical inputs MUST produce identical outputs
introduced projection-level contract tests to enforce:
* determinism across repeated builds
* correct handling of empty streams
* correct ordering sensitivity
* stable state reconstruction semantics
---
### 4. test fixtures & reusable contract infrastructure
introduced shared testing primitives to enforce consistency across modules.
components:
* deterministic `stored()` / `event()` builders
* reusable EventStore contract test suite
* reusable replay contract test suite
* projection contract test base class
ensures:
> correctness rules are defined once and enforced everywhere
---
### 5. concurrency correctness validation layer
formalized and tested concurrency guarantees for EventStore implementations.
validated properties:
* safe concurrent appends
* absence of race-condition-based sequence corruption
* uniqueness enforcement under parallel writes
* deterministic final stream state under concurrent load
introduced stress-style test utilities for reproducible concurrency validation.
---
### 6. projection infrastructure alignment
aligned projection system with replay engine from Epic 2 while maintaining strict separation of concerns.
ensured:
* projections depend only on event stream abstraction
* projections remain implementation-agnostic
* replay engine is the only execution driver
* no direct store coupling inside projection logic
this created a clean boundary:
```text id="p1"
EventStore → EventReplayer → Projection → State
```
---
### 7. event system boundary tightening
refined event system usage rules across all layers:
* event payload remains the only domain extension point
* metadata is strictly non-semantic
* sequence is the only ordering primitive
* causality is informational only (not execution-driving)
ensured event system remains the **single authoritative truth layer** without semantic leakage.
---
### 8. architectural separation enforcement
formalized module separation rules:
* `core/events` → source of truth + replay primitives
* `core/events/projections` → deterministic state builders
* `infrastructure/persistence` → storage implementations only
* `testing/contracts` → cross-module correctness enforcement
enforced rule:
> no module may assume correctness of another without contract validation
---
### 9. replay correctness guarantee foundation
strengthened replay guarantees introduced in Epic 2:
* deterministic replay across time and environment
* backend-independent state reconstruction
* full reproducibility of session state from event stream
* elimination of hidden state assumptions in projection lifecycle
Epic 1.5 made replay correctness **explicitly testable and enforced**, not implicit.
---
# final architecture after Epic 1.5
```text id="epic15"
EventStore (contract-enforced)
StoredEvent stream (ordered, idempotent)
EventReplayer (deterministic engine)
Projection contracts (pure functions)
State (reconstructed, disposable)
```
---
# major architectural outcomes
Epic 1.5 established:
* formal correctness contracts for EventStore and replay systems
* deterministic projection enforcement layer
* reusable cross-module test contract infrastructure
* concurrency-safe event persistence validation
* strict replay determinism guarantees across implementations
* hardened separation between storage, replay, and projection layers
---
# what Epic 1.5 intentionally does NOT include
not implemented:
* session lifecycle FSM (Epic 2)
* transition engine execution semantics (Epic 3)
* workflow orchestration
* runtime kernel
* tool execution system
* policy/approval integration
those systems are explicitly higher-level and depend on this layer being stable.
---
# final state
Correx now has:
> a formally contract-enforced event sourcing foundation with deterministic replay, validated projection semantics, and strict cross-implementation guarantees ensuring that all state reconstruction logic is reproducible, testable, and backend-independent.
+178
View File
@@ -0,0 +1,178 @@
# Epic 1.5: Event Store Hardening (Concurrency, Integrity, and Replay Safety)
**status:** proposed
**date:** 08.05.2026 (May)
**scope:** `:core:events`, `:infrastructure:persistence`
---
## context
Epic 1 establishes a functional event-sourced storage system (in-memory + SQLite) with correct basic semantics: append, read, ordering, and idempotency.
However, current implementation assumes ideal conditions:
* single-threaded or externally synchronized writes
* stable event schema
* no replay pressure under large histories
* no concurrent access patterns
These assumptions do not hold once the system is integrated into orchestration (`:core:kernel`) and async execution (`coroutines`, CLI + server).
Epic 1.5 introduces **hard guarantees required for real execution environments**, without changing the external EventStore contract.
---
## goal
Make the event store:
> deterministic, concurrency-safe, replay-stable, and schema-resilient under real execution conditions
---
## scope (what IS included)
### 1. concurrency model definition
Define and enforce:
* single-writer guarantee per EventStore instance
* behavior under concurrent `append` / `appendAll`
* read consistency rules during writes
**deliverable:**
* documented concurrency contract in `EventStore`
---
### 2. SQLite transactional correctness
Ensure SQLite implementation guarantees:
* atomic `appendAll` (single transaction boundary)
* no partial writes on failure
* deterministic ordering under concurrent calls (within single instance constraints)
**work:**
* explicit transaction wrapping
* `BEGIN/COMMIT/ROLLBACK` safety handling
* removal of implicit autocommit ambiguity
---
### 3. sequence integrity under concurrency
Guarantee:
* per-session sequence monotonicity
* no race-condition-based sequence duplication
* consistent `lastSequence()` computation under concurrent writes
**possible approaches:**
* synchronized write lock per session
* or single global write lock (simpler, acceptable for v1)
---
### 4. contract test hardening (EventStoreContractTest upgrade)
Expand contract tests to include:
* concurrent append simulation
* appendAll atomicity verification
* read consistency during write bursts
* deterministic replay under repeated execution
---
### 5. snapshot preparation layer (interface only)
Introduce:
* `SnapshotStore` interface (no full implementation required yet)
* hook points in EventStore for snapshot triggers
This is structural preparation only.
---
### 6. event schema version discipline
Enforce:
* `version` field is mandatory and meaningful
* serialization must be version-aware
* unknown version behavior defined (fail fast initially)
No migration logic yet, only rules.
---
### 7. replay determinism guarantee
Formalize:
> replay of the same event stream must produce identical projections regardless of store implementation
This becomes a top-level invariant.
---
## explicit exclusions (important)
Epic 1.5 does NOT include:
* sessions / FSM logic
* transitions / graph engine
* approval system evolution
* context processing
* inference orchestration
* distributed storage (Kafka, Redis, etc.)
---
## consequences
### positive
* removes race-condition ambiguity in kernel integration
* guarantees EventStore correctness under async execution
* makes replay safe for debugging and dataset generation
* stabilizes foundation for all higher-level epics
### negative
* introduces stricter constraints on store implementations
* adds concurrency complexity to SQLite layer
* increases test surface area significantly
* forces early clarity on threading model (good, but non-trivial)
---
## rationale
Without this layer, higher-level systems will assume inconsistent event semantics depending on:
* execution timing
* store implementation
* concurrency patterns in kernel
This breaks the core promise of Correx:
> deterministic replayable execution over nondeterministic inference systems
Epic 1.5 ensures the event layer is not just correct, but **operationally invariant**.
---
## status
Recommended immediately after Epic 1 completion and before introduction of:
* `:core:sessions`
* `:core:kernel`
* `:core:transitions`
+193
View File
@@ -0,0 +1,193 @@
# Epic 1: Event System Core (Append-only Log Foundation)
**status:** accepted
**date:** 07.05.2026 (May)
**scope:** `:core:events`
---
## context
Correx is built on deterministic replay of nondeterministic execution. The only reliable source of truth is a structured event log.
To support:
* reproducible session execution
* crash recovery
* offline debugging
* synthetic data generation
the system requires a strict event sourcing foundation.
This epic establishes the minimal, correct event system abstraction before any orchestration logic exists.
---
## goal
Define and implement a **fully functional event sourcing core** that provides:
> an append-only, ordered, immutable event log with deterministic replay capability
---
## scope (what IS included)
### 1. event model definition
Define the canonical event structure:
* `EventEnvelope`
* `eventId`
* `sessionId`
* `sequence`
* `timestamp`
* `version`
* `causationId`
* `correlationId`
* `payload`
### 2. payload abstraction
Define `EventPayload` interface for polymorphic event content.
Implement concrete event types (initial minimal set):
* tool invocation event
* approval event (minimal placeholder)
* session lifecycle event (minimal placeholder)
No domain expansion beyond structural needs.
---
### 3. serialization system
Implement deterministic serialization using `kotlinx.serialization`:
* polymorphic module for `EventPayload`
* stable JSON encoding (`Json`)
* version field included for forward compatibility
Guarantee:
> identical event → identical serialized form
---
### 4. EventStore interface
Define core contract:
* `append(event)`
* `appendAll(events)`
* `read(sessionId): List<EventEnvelope>`
* `lastSequence(sessionId): Long`
Rules:
* append-only semantics
* no mutation or update operations
* ordering guaranteed per session
---
### 5. in-memory EventStore implementation
Provide reference implementation:
* deterministic ordering per session
* idempotency via eventId deduplication
* sequence generation per session
* strict ordering enforcement
Used for:
* contract testing
* fast execution
* deterministic simulation
---
### 6. SQLite EventStore implementation (v1 baseline)
Provide persistence-backed implementation:
* append events into SQLite table
* enforce uniqueness via `event_id`
* store sequence per session
* support ordered reads via SQL query
No concurrency guarantees in Epic 1 (deferred to Epic 1.5).
---
### 7. contract test suite (EventStoreContractTest)
Define shared behavior validation:
* ordering is preserved
* idempotency rules are consistent
* read returns correct sequence
* appendAll preserves ordering semantics
* both implementations behave identically
This becomes the **canonical correctness definition**.
---
## explicit exclusions (important)
Epic 1 does NOT include:
* concurrency safety guarantees
* snapshotting
* transaction management hardening
* session lifecycle logic
* workflow transitions
* orchestration kernel
* distributed storage
* performance optimization
* schema migration system
---
## consequences
### positive
* establishes single source of truth for all system state
* enables deterministic replay foundation
* allows testing higher-level logic via event streams
* decouples domain logic from persistence mechanics
* provides interchangeable storage backends
### negative
* naive SQLite implementation may not reflect production concurrency needs
* no snapshot support → replay cost increases over time
* schema evolution is manually managed initially
* requires strict discipline to avoid leaking mutable state concepts upward
---
## rationale
Event sourcing must be introduced as a **pure, minimal abstraction first**, without mixing:
* orchestration logic
* concurrency concerns
* optimization layers
This ensures that all higher-level systems depend on a stable, predictable contract rather than implementation behavior.
---
## status
Epic 1 is the **foundation layer of Correx architecture**.
All subsequent epics (sessions, transitions, kernel, approvals, context) assume:
> EventStore is correct, deterministic, and interchangeable across implementations.
+277
View File
@@ -0,0 +1,277 @@
# Epic 10 — Orchestration Kernel (Lifecycle, Retry, Replay)
## completed deliverables
### 1. orchestration state and status model
implemented a strict state model for orchestration lifecycle tracking.
final structures:
* `OrchestrationStatus` (enum)
* `OrchestrationState`
key properties:
* immutable status transitions
* explicit lifecycle tracking (IDLE → RUNNING → PAUSED/COMPLETED/FAILED)
* retry count tracking
* pause reason tracking
* pending approval flag
* failure reason tracking
status values:
* IDLE — initial state, awaiting workflow start
* RUNNING — active stage execution
* PAUSED — awaiting approval or user action
* COMPLETED — successful workflow termination
* FAILED — workflow terminated due to error
* CANCELED — workflow terminated due to cancellation
---
### 2. workflow event model
introduced workflow lifecycle events as the orchestration backbone.
final structure:
* `WorkflowStartedEvent` — marks workflow initiation
* `WorkflowCompletedEvent` — marks successful completion
* `WorkflowFailedEvent` — marks failure with reason and retry context
properties:
* explicit lifecycle boundaries
* causality-safe event emission
* retry-exhausted tracking for failure events
---
### 3. orchestration pause/resume events
introduced explicit pause/resume semantics for approval workflows.
final structure:
* `OrchestrationPausedEvent` — marks pause with reason (APPROVAL_PENDING, USER_REQUESTED)
* `OrchestrationResumedEvent` — marks resumption after approval
properties:
* explicit pause reasons for auditability
* stage-aware pause tracking
* deterministic resume semantics
---
### 4. inference state management (read-only projection)
implemented inference state as a read-only projection for auditability.
final structures:
* `InferenceRecord` — immutable inference attempt snapshot
* `InferenceState` — collection of inference records
* `InferenceReducer` / `DefaultInferenceReducer`
* `InferenceProjector`
* `InferenceRepository`
key properties:
* append-only inference history
* per-request tracking (requestId, providerId, stageId)
* status tracking (started, completed, failed, timed out)
* token usage and latency recording
* failure reason persistence
---
### 5. retry coordination system
implemented deterministic retry logic with exponential backoff support.
final structures:
* `RetryPolicy` — configures maxAttempts and backoffMs
* `RetryCoordinator` / `DefaultRetryCoordinator`
* `RetryAttemptedEvent` — audit trail for retry attempts
key properties:
* attempt-based retry control
* configurable backoff delays
* event-emitted retry tracking
* failure reason preservation
---
### 6. replay orchestrator (deterministic execution)
implemented deterministic replay for testing and audit scenarios.
final structures:
* `ReplayOrchestrator` — concrete implementation of `SessionOrchestrator`
* `ReplayInferenceProvider` — artifact-based inference
* `ReplayArtifactMissingException` — replay failure signal
* `ReplayStrategy` — SkipInference, SkipValidation, Full
key properties:
* bypasses live inference, uses recorded artifacts
* deterministic output for testing
* strategy-configurable replay depth
* exception-based artifact missing detection
---
### 7. session orchestrator abstraction
implemented abstract base for orchestration implementations.
final structures:
* `SessionOrchestrator` (abstract) — base interface
* `DefaultSessionOrchestrator` — concrete implementation
key properties:
* unified stage execution model
* inference routing integration
* validation pipeline integration
* approval engine integration
* event emission abstraction
* cancellation support
---
### 8. stage execution and outcome model
implemented stage execution result types for orchestration decisions.
final structure:
* `StageOutcome` (sealed interface, renamed from StageExecutionResult)
* `Success` — stage completed with artifact
* `ValidationFailure` — validation failed, retryable flag
* `InferenceFailure` — inference failed, retryable flag
* `ApprovalRequired` — approval pending
* `Cancelled` — stage cancelled
properties:
* outcome-based decision making
* retryability metadata
* artifact capture for replay
---
### 9. orchestration configuration
introduced configurable orchestration parameters.
final structure:
* `OrchestrationConfig`
key properties:
* `retryPolicy` — RetryPolicy instance
* `replayStrategy` — ReplayStrategy instance
* `stageTimeoutMs` — per-stage timeout configuration
---
### 10. serialization system (orchestration events)
registered all orchestration events in the serialization module.
components:
* `Serialization.kt` in `:core:events`
* 6 new orchestration events registered:
* `WorkflowStartedEvent`
* `WorkflowCompletedEvent`
* `WorkflowFailedEvent`
* `OrchestrationPausedEvent`
* `OrchestrationResumedEvent`
* `RetryAttemptedEvent`
* `InferenceStatus``TIMED_OUT` added
---
### 11. testing fixtures (extended)
extended test fixtures for comprehensive coverage.
components:
* `TransitionFixtures`
* `InferenceFixtures`
* `ContextFixtures`
* `ValidationFixtures`
---
# final architecture after Epic 10
```text id="e10"
Orchestration Kernel
├── SessionOrchestrator (abstract base)
│ ├── DefaultSessionOrchestrator
│ └── ReplayOrchestrator
├── State Management
│ ├── OrchestrationState
│ ├── InferenceState
│ └── Event Replayer
├── Retry System
│ ├── RetryPolicy
│ ├── RetryCoordinator
│ └── DefaultRetryCoordinator
└── Replay Support
├── ReplayStrategy
├── ReplayInferenceProvider
└── ReplayArtifactMissingException
```
---
# major architectural outcomes
Epic 10 established:
* orchestration lifecycle management
* deterministic replay capability
* retry coordination with audit trail
* inference state projection
* stage outcome model
* orchestrator abstraction layer
* pause/resume semantics
---
# what Epic 10 intentionally does NOT include
not implemented:
* live inference providers (replay only)
* approval workflow execution (abstraction only)
* stage timeout enforcement (config only)
* context budget enforcement (TODO epic-11)
* risk model integration (TODO epic-11)
those are explicitly deferred to Epic 11+
---
# final state
Correx now has:
> a complete orchestration kernel with lifecycle management, retry coordination, deterministic replay, and inference state projection, enabling both live execution and reproducible test scenarios.
+685
View File
@@ -0,0 +1,685 @@
# Epic 11 — Infrastructure Layer
**date:** 2026-05-12
**scope:** `infrastructure/` — persistence, inference, tools, model management
**status:** ready to implement
---
## resume instructions
1. open project at `/home/kami/Programs/correx`
2. read this file top to bottom before writing any code
3. complete tasks in order — hard boundary after task 3 before tools work begins
4. do NOT explore beyond files listed in each task
5. in-memory stores and mocks are NOT acceptable — this epic is real implementations only
---
## prerequisite — `:core:tools` contract extension (before epic 11)
these three additions belong in `:core:tools`, not infrastructure. complete them first as a separate commit before touching any `infrastructure/` code.
### `ToolResult`
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolResult.kt`
```kotlin
sealed interface ToolResult {
data class Success(
val invocationId: ToolInvocationId,
val output: String,
val metadata: Map<String, String> = emptyMap(),
) : ToolResult
data class Failure(
val invocationId: ToolInvocationId,
val reason: String,
val recoverable: Boolean,
) : ToolResult
}
```
### `ToolExecutor`
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolExecutor.kt`
```kotlin
interface ToolExecutor {
suspend fun execute(request: ToolRequest): ToolResult
}
```
### `FileAffectingTool`
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/FileAffectingTool.kt`
marker interface for tools that touch the filesystem. sandbox uses this to know which paths to back up:
```kotlin
interface FileAffectingTool : Tool {
fun affectedPaths(request: ToolRequest): Set<Path>
}
```
---
## scope (what IS in epic 11)
- `SqliteEventStore` — already done, skip
- `LlamaCppInferenceProvider` — OpenAI-compatible REST client
- `LlamaCppTokenizer`
- `ModelManager` + `ManagedInferenceProvider` — load/unload/health/residency
- `ToolRegistry` — immutable after bootstrap
- `SandboxedToolExecutor``/tmp` isolation + backup/restore + approval gating
- `ShellTool` — argv-based, allowlist-gated
- `FilesystemTool` — path-allowlisted, traversal-safe
- `InfrastructureModule` — wiring
## scope (what is NOT in epic 11)
- postgres, snapshots, migrations
- ollama / vllm providers
- docker / network tools
- telemetry exporters
- process namespace isolation (seccomp/unshare) — parked
- scheduler / concurrency / backpressure
- CLI / API wiring
---
## dependency inventory
| module | key types | concrete import | file path |
|---|---|---|---|
| `:core:events` | `EventStore` | `import com.correx.core.events.stores.EventStore` | `core/events/src/main/kotlin/com/correx/core/events/stores/EventStore.kt` |
| `:core:events` | `NewEvent` | `import com.correx.core.events.events.NewEvent` | `core/events/src/main/kotlin/com/correx/core/events/events/EventEnvelope.kt` |
| `:core:events` | `EventMetadata` | `import com.correx.core.events.events.EventMetadata` | `core/events/src/main/kotlin/com/correx/core/events/events/EventMetadata.kt` |
| `:core:events` | `TypeId` | `import com.correx.core.utils.TypeId` | `core/events/src/main/kotlin/com/correx/core/utils/TypeId.kt` |
| `:core:inference` | `InferenceProvider` | `import com.correx.core.inference.InferenceProvider` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceProvider.kt` |
| `:core:inference` | `InferenceRequest` | `import com.correx.core.inference.InferenceRequest` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceRequest.kt` |
| `:core:inference` | `InferenceResponse` | `import com.correx.core.inference.InferenceResponse` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt` |
| `:core:inference` | `InferenceRouter` | `import com.correx.core.inference.InferenceRouter` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceRouter.kt` |
| `:core:inference` | `GenerationConfig` | `import com.correx.core.inference.GenerationConfig` | `core/inference/src/main/kotlin/com/correx/core/inference/GenerationConfig.kt` |
| `:core:inference` | `TokenUsage` | `import com.correx.core.inference.TokenUsage` | `core/events/src/main/kotlin/com/correx/core/inference/TokenUsage.kt` |
| `:core:inference` | `FinishReason` | `import com.correx.core.inference.FinishReason` | `core/inference/src/main/kotlin/com/correx/core/inference/FinishReason.kt` |
| `:core:inference` | `ProviderId` | `import com.correx.core.events.types.ProviderId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
| `:core:inference` | `ModelCapability` | `import com.correx.core.inference.ModelCapability` | `core/inference/src/main/kotlin/com/correx/core/inference/ModelCapability.kt` |
| `:core:inference` | `CapabilityScore` | `import com.correx.core.inference.CapabilityScore` | `core/inference/src/main/kotlin/com/correx/core/inference/ModelCapability.kt` |
| `:core:inference` | `ProviderHealth` | `import com.correx.core.inference.ProviderHealth` | `core/inference/src/main/kotlin/com/correx/core/inference/ProviderHealth.kt` |
| `:core:approvals` | `ApprovalEngine` | `import com.correx.core.approvals.domain.ApprovalEngine` | `core/approvals/src/main/kotlin/com/correx/core/approvals/domain/ApprovalEngine.kt` |
| `:core:approvals` | `ApprovalRequest` | `import com.correx.core.approvals.model.DomainApprovalRequest` | `core/approvals/src/main/kotlin/com/correx/core/approvals/model/DomainApprovalRequest.kt` |
| `:core:approvals` | `ApprovalContext` | `import com.correx.core.approvals.model.ApprovalContext` | `core/approvals/src/main/kotlin/com/correx/core/approvals/model/ApprovalContext.kt` |
| `:core:approvals` | `ApprovalMode` | `import com.correx.core.sessions.ApprovalMode` | `core/sessions/src/main/kotlin/com/correx/core/sessions/ApprovalMode.kt` |
| `:core:approvals` | `Tier` | `import com.correx.core.approvals.Tier` | `core/events/src/main/kotlin/com/correx/core/approvals/Tier.kt` |
| `:core:tools` | `Tool` | `import com.correx.core.tools.contract.Tool` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/Tool.kt` |
| `:core:tools` | `ToolRequest` | `import com.correx.core.events.events.ToolRequest` | `core/events/src/main/kotlin/com/correx/core/events/events/ToolRequest.kt` |
| `:core:tools` | `ToolInvocationId` | `import com.correx.core.events.types.ToolInvocationId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
| `:core:tools` | `ToolResult` | `import com.correx.core.tools.contract.ToolResult` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolResult.kt` |
| `:core:tools` | `ToolExecutor` | `import com.correx.core.tools.contract.ToolExecutor` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolExecutor.kt` |
| `:core:tools` | `FileAffectingTool` | `import com.correx.core.tools.contract.FileAffectingTool` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/FileAffectingTool.kt` |
| `:core:sessions` | `SessionId` | `import com.correx.core.events.types.SessionId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
| `:core:sessions` | `StageId` | `import com.correx.core.events.types.StageId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
| `infrastructure:persistence:sqlite` | `SqliteEventStore` | `import com.correx.infrastructure.persistence.sqlite.SqliteEventStore` | `infrastructure/persistence/src/main/kotlin/com/correx/infrastructure/persistence/SqliteEventStore.kt` |
---
## task 1 — `LlamaCppInferenceProvider`
**module:** `infrastructure:inference:llama_cpp`
**location:** `infrastructure/inference/llama_cpp/`
### what it does
calls llama-server's OpenAI-compatible REST API. implements `InferenceProvider`.
endpoints (constructed from `baseUrl = "http://127.0.0.1:10000"`):
- inference: `$baseUrl/v1/chat/completions`
- health: `$baseUrl/health`
- tokenize: `$baseUrl/tokenize`
### build deps
```groovy
implementation project(':core:inference')
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-cio:$ktor_version"
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
```
### request/response mapping
```kotlin
@Serializable
data class ChatCompletionRequest(
val model: String,
val messages: List<ChatMessage>,
val temperature: Double,
@SerialName("top_p") val topP: Double,
@SerialName("max_tokens") val maxTokens: Int,
@SerialName("stop") val stopSequences: List<String> = emptyList(),
val seed: Long? = null,
val stream: Boolean = false,
)
@Serializable
data class ChatMessage(val role: String, val content: String)
@Serializable
data class ChatCompletionResponse(
val id: String,
val choices: List<Choice>,
val usage: Usage,
)
@Serializable
data class Choice(
val message: ChatMessage,
@SerialName("finish_reason") val finishReason: String,
)
@Serializable
data class Usage(
@SerialName("prompt_tokens") val promptTokens: Int,
@SerialName("completion_tokens") val completionTokens: Int,
@SerialName("total_tokens") val totalTokens: Int,
)
```
### context pack → messages mapping
- L0 (system) → `role = "system"`
- L1 (task) → `role = "user"`
- L2 (history/tools) → `role = "assistant"` or `role = "user"` depending on `sourceType`
- if `ContextPack` is empty → single `user` message with empty string (do not crash)
### `LlamaCppInferenceProvider`
```kotlin
class LlamaCppInferenceProvider(
private val modelId: String,
private val baseUrl: String = "http://127.0.0.1:10000",
private val httpClient: HttpClient = defaultHttpClient(),
) : InferenceProvider {
override val id: ProviderId = ProviderId("llama-cpp:$modelId")
override val tokenizer: Tokenizer = LlamaCppTokenizer(baseUrl, httpClient)
override suspend fun infer(request: InferenceRequest): InferenceResponse
override fun healthCheck(): ProviderHealth
override fun capabilities(): Set<CapabilityScore>
}
```
### `healthCheck()`
GET `$baseUrl/health`. 200 → `ProviderHealth.Healthy`, otherwise `ProviderHealth.Unhealthy`.
### `capabilities()`
hardcoded, pattern-matched on `modelId`:
- contains "coder" → `ModelCapability.CODING` score 0.9
- contains "r1" or "deepseek" → `ModelCapability.REASONING` score 0.9
- default → `ModelCapability.GENERAL` score 0.7
### constraints
- `stream = false` always
- do NOT implement retry logic — `RetryCoordinator`'s responsibility
- do NOT catch `CancellationException` — let it propagate
- throw `InferenceProviderException` on non-2xx HTTP responses
- `latencyMs` = wall clock time of the HTTP call
---
## task 2 — `LlamaCppTokenizer`
**module:** `infrastructure:inference:llama_cpp`
```kotlin
class LlamaCppTokenizer(
private val baseUrl: String,
private val httpClient: HttpClient,
) : Tokenizer {
override fun tokenize(text: String): List<Int>
override fun count(text: String): Int = tokenize(text).size
}
```
POST `$baseUrl/tokenize` with `{"content": text}`, parse `{"tokens": [...]}`.
fallback: if endpoint unavailable → approximate `text.length / 4`, log warning, do NOT throw.
---
## task 3 — `ModelManager` + `ManagedInferenceProvider`
**module:** `infrastructure:inference:llama_cpp`
manages llama-server process lifecycle. single model at a time, enforced by `Mutex`.
### residency modes
```kotlin
enum class ResidencyMode {
PERSISTENT, // never unload
DYNAMIC, // unload after idle timeout
EPHEMERAL, // unload immediately after infer completes
}
```
### `ModelDescriptor`
```kotlin
data class ModelDescriptor(
val modelId: String,
val modelPath: String,
val residencyMode: ResidencyMode,
val idleTimeoutMs: Long = 60_000L,
val contextSize: Int = 8192,
)
```
### `ModelManager` interface
```kotlin
interface ModelManager {
suspend fun load(descriptor: ModelDescriptor): InferenceProvider
suspend fun unload(modelId: String)
fun currentModel(): ModelDescriptor?
fun healthCheck(): ProviderHealth
}
```
### `DefaultModelManager`
model swap via **process restart** — kill existing llama-server, spawn new with `--model` flag.
```kotlin
class DefaultModelManager(
private val llamaServerBin: String = "llama-server",
private val host: String = "127.0.0.1",
private val port: Int = 10000,
private val healthTimeoutMs: Long = 30_000L,
private val eventStore: EventStore,
) : ModelManager
```
**`load()` flow — event appended AFTER committed state:**
1. acquire `Mutex`
2. if `currentDescriptor?.modelId == descriptor.modelId` → no-op, return existing provider
3. if process running: `process.destroyForcibly()`, wait for exit, clear state, append `ModelUnloadedEvent`
4. spawn: `ProcessBuilder(llamaServerBin, "--model", descriptor.modelPath, "--ctx-size", descriptor.contextSize.toString(), "--host", host, "--port", port.toString())`
5. redirect stdout/stderr to `~/.local/share/correx/logs/llama-server.log` — create dir if absent
6. poll GET `http://$host:$port/health` until 200 — if `healthTimeoutMs` exceeded: throw `ModelLoadException` (no event emitted on failure)
7. set `currentProcess`, `currentDescriptor`
8. append `ModelLoadedEvent` ← only after process is healthy and state committed
9. release `Mutex`, return `ManagedInferenceProvider(delegate, this, descriptor)`
**`unload()` flow — event appended AFTER committed state:**
1. acquire `Mutex`
2. `process.destroyForcibly()`, wait for exit
3. clear `currentProcess`, `currentDescriptor`
4. append `ModelUnloadedEvent` ← only after process is dead and state cleared
5. release `Mutex`
### `ManagedInferenceProvider`
wraps `LlamaCppInferenceProvider`, notifies manager for residency policy:
```kotlin
class ManagedInferenceProvider(
private val delegate: InferenceProvider,
private val manager: DefaultModelManager,
private val descriptor: ModelDescriptor,
) : InferenceProvider by delegate {
override suspend fun infer(request: InferenceRequest): InferenceResponse {
manager.touch(descriptor.modelId) // reset DYNAMIC idle timer
val response = delegate.infer(request)
manager.onInferenceCompleted(descriptor) // trigger EPHEMERAL unload if applicable
return response
}
}
```
`DefaultModelManager` internal methods (not on `ModelManager` interface):
- `touch(modelId)` — resets DYNAMIC idle timer coroutine
- `onInferenceCompleted(descriptor)` — calls `unload()` immediately if `EPHEMERAL`, no-op otherwise
**DYNAMIC timer behaviour:**
- timer coroutine starts on first `touch()` after load
- each `touch()` cancels and restarts the timer
- on timeout: calls `unload()`
### constraints
- do NOT manage GPU memory directly — llama-server handles that
- do NOT emit events before process state is committed — event log must reflect truth, not intent
- `ModelLoadException` thrown on health timeout — no event emitted in this case
- spawned process log dir created on first use if absent
---
## task 4 — `ToolRegistry`
**module:** `infrastructure:tools`
immutable after construction:
```kotlin
class ToolRegistry private constructor(
private val tools: Map<String, Tool>,
) {
fun resolve(name: String): Tool? = tools[name]
fun all(): List<Tool> = tools.values.toList()
companion object {
fun build(vararg tools: Tool): ToolRegistry =
ToolRegistry(tools.associateBy { it.name })
fun build(tools: List<Tool>): ToolRegistry =
ToolRegistry(tools.associateBy { it.name })
}
}
```
no `register()` method — `private constructor` enforces bootstrap-only population.
---
## task 5 — `SandboxedToolExecutor`
**module:** `infrastructure:tools`
```kotlin
class SandboxedToolExecutor(
private val delegate: ToolExecutor,
private val registry: ToolRegistry,
private val approvalEngine: ApprovalEngine,
private val eventStore: EventStore,
private val workDir: Path = Path("/tmp/correx-sandbox"),
) : ToolExecutor {
override suspend fun execute(request: ToolRequest): ToolResult
}
```
execution flow:
1. resolve tool from `registry` by `request.toolName` — if not found: return `ToolResult.Failure(recoverable = false)`
2. check approval via `tool.tier`:
```kotlin
when (tool.tier) {
Tier.T0, Tier.T1 -> { /* proceed */ }
Tier.T2, Tier.T3 -> { /* evaluate approval */ }
}
```
if not approved: emit `ToolExecutionRejectedEvent`, return `ToolResult.Failure(recoverable = false)`
3. emit `ToolExecutionStartedEvent`
4. create working dir: `/tmp/correx-sandbox/{sessionId}/{invocationId}/`
5. if tool implements `FileAffectingTool`: call `tool.affectedPaths(request)`, copy each to `{workDir}/{filename}.bak` via `Files.copy`
6. if backup fails: return `ToolResult.Failure` before delegating — do NOT proceed
7. delegate to underlying `ToolExecutor`
8. on `ToolResult.Success`: `Files.move` result to original path, delete `.bak` files, emit `ToolExecutionCompletedEvent`
9. on `ToolResult.Failure`: restore `.bak` files via `Files.move`, delete working dir, emit `ToolExecutionFailedEvent`
### constraints
- working dir is fresh per invocation — no shared state
- `.bak` files always cleaned up — no leaks regardless of outcome
- do NOT use `File.deleteOnExit()` — explicit cleanup only
- backup/restore uses `Files.copy` + `Files.move` — not shell commands
---
## task 6 — `ShellTool`
**module:** `infrastructure:tools:shell`
uses `argv: List<String>` — no shell string parsing, no shell invocation.
```kotlin
class ShellTool(
private val allowedExecutables: Set<String> = emptySet(),
private val timeoutMs: Long = 30_000L,
) : Tool, ToolExecutor {
override val name: String = "shell"
override val tier: Tier = Tier.T2
override val requiredCapabilities: Set<ToolCapability> =
setOf(ToolCapability.SHELL_EXEC, ToolCapability.PROCESS_SPAWN)
override fun validateRequest(request: ToolRequest): ValidationResult
override suspend fun execute(request: ToolRequest): ToolResult
}
```
`execute()`:
- extract `argv: List<String>` from `request.parameters["argv"]`
- first element is the executable — validate against `allowedExecutables`
- if not in allowlist: return `ToolResult.Failure(recoverable = false)` immediately
- spawn: `ProcessBuilder(argv).apply { environment().clear() }`
- capture stdout + stderr separately
- enforce `timeoutMs` — `process.destroyForcibly()` on timeout, return `ToolResult.Failure`
- exit code 0 → `ToolResult.Success(output = stdout)`
- non-zero → `ToolResult.Failure(reason = stderr, recoverable = false)`
### constraints
- do NOT invoke `bash -c` or any shell — `ProcessBuilder(argv)` directly
- do NOT use `Runtime.exec()`
- do NOT inherit parent process environment — `environment().clear()` explicitly
- stdout and stderr captured, never forwarded to parent stdout
---
## task 7a — `FileReadTool`
**file:** `infrastructure/tools/filesystem/FileReadTool.kt`
**module:** `infrastructure:tools:filesystem`
```kotlin
class FileReadTool(
private val allowedPaths: Set<Path> = emptySet(),
) : Tool {
override val name: String = "file_read"
override val tier: Tier = Tier.T1
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_READ)
override fun validateRequest(request: ToolRequest): ValidationResult
}
```
also implements `ToolExecutor` directly.
supported operations (from `request.parameters["operation"]`):
- `read` — read file contents, return as `output`
- `list` — list directory contents at `path`
- `exists` — return `"true"` or `"false"`
does NOT implement `FileAffectingTool` — read operations require no backup.
path validation before ANY operation:
1. extract `path` from `request.parameters["path"]`
2. resolve to absolute via `toRealPath()`
3. check resolved path starts with at least one `allowedPaths` root
4. if not: return `ToolResult.Failure(recoverable = false)`
### constraints
- `allowedPaths` empty = deny ALL — fail-secure default
- `toRealPath()` resolves symlinks — re-validate after resolution
- do NOT follow symlinks outside allowed paths
- T1 — goes through `SandboxedToolExecutor` approval check but auto-approved
---
## task 7b — `FileWriteTool`
**file:** `infrastructure/tools/filesystem/FileWriteTool.kt`
**module:** `infrastructure:tools:filesystem`
```kotlin
class FileWriteTool(
private val allowedPaths: Set<Path> = emptySet(),
) : Tool, FileAffectingTool {
override val name: String = "file_write"
override val tier: Tier = Tier.T2
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_WRITE)
override fun affectedPaths(request: ToolRequest): Set<Path>
override fun validateRequest(request: ToolRequest): ValidationResult
}
```
also implements `ToolExecutor`.
supported operations:
- `write` — create or fully overwrite file at `path` with `content` parameter
- `delete` — delete file at `path`
`affectedPaths()` — extract `path` from request, resolve absolute, return as singleton set.
path validation — same as `FileReadTool` but applied to write targets.
### constraints
- `allowedPaths` empty = deny ALL
- `toRealPath()` on parent dir for new files — target file may not exist yet, validate parent
- T2 — requires approval via `SandboxedToolExecutor`
- backup/restore handled by `SandboxedToolExecutor` via `FileAffectingTool` — do NOT implement backup here
---
## task 7c — `FileEditTool`
**file:** `infrastructure/tools/filesystem/FileEditTool.kt`
**module:** `infrastructure:tools:filesystem`
```kotlin
class FileEditTool(
private val allowedPaths: Set<Path> = emptySet(),
) : Tool, FileAffectingTool {
override val name: String = "file_edit"
override val tier: Tier = Tier.T3
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_WRITE)
override fun affectedPaths(request: ToolRequest): Set<Path>
override fun validateRequest(request: ToolRequest): ValidationResult
}
```
also implements `ToolExecutor`.
supported operations (from `request.parameters["operation"]`):
- `append` — append `content` to end of existing file
- `replace` — find `target` string in file, replace with `replacement` — fails if `target` not found or matches multiple times
- `patch` — apply a unified diff from `patch` parameter via `ProcessBuilder("patch", "-p0")` — T3 because patch application is the hardest to verify
`affectedPaths()` — same as `FileWriteTool`, returns target path as singleton.
path validation — same pattern, file must exist for all operations.
### constraints
- `allowedPaths` empty = deny ALL
- `replace` must match exactly once — if zero or multiple matches: `ToolResult.Failure(recoverable = false)`
- `patch` invokes external `patch` binary via `ProcessBuilder` — do NOT use shell
- T3 — highest approval tier for file tools
- backup/restore handled by `SandboxedToolExecutor` — always backs up before any edit
---
## task 8 — integration wiring
**module:** `infrastructure`
```kotlin
object InfrastructureModule {
fun createEventStore(dbPath: String): EventStore =
SqliteEventStore(DriverManager.getConnection("jdbc:sqlite:$dbPath"))
fun createModelManager(eventStore: EventStore): ModelManager =
DefaultModelManager(eventStore = eventStore)
fun createToolRegistry(config: ToolConfig): ToolRegistry =
ToolRegistry.build(buildList {
if (config.shell.enabled) add(ShellTool(config.shell.allowedExecutables))
if (config.fileRead.enabled) add(FileReadTool(config.fileRead.allowedPaths))
if (config.fileWrite.enabled) add(FileWriteTool(config.fileWrite.allowedPaths))
if (config.fileEdit.enabled) add(FileEditTool(config.fileEdit.allowedPaths))
})
fun createToolExecutor(
registry: ToolRegistry,
approvalEngine: ApprovalEngine,
eventStore: EventStore,
): ToolExecutor = SandboxedToolExecutor(
delegate = DispatchingToolExecutor(registry),
registry = registry,
approvalEngine = approvalEngine,
eventStore = eventStore,
)
}
```
`DispatchingToolExecutor` — resolves tool from registry by `request.toolName`, delegates. returns `ToolResult.Failure` if not found.
`ToolConfig` stub until epic 12:
```kotlin
data class ToolConfig(
val shell: ShellConfig = ShellConfig(),
val fileRead: FileReadConfig = FileReadConfig(),
val fileWrite: FileWriteConfig = FileWriteConfig(),
val fileEdit: FileEditConfig = FileEditConfig(),
)
data class FileReadConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
data class FileWriteConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
data class FileEditConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
data class ShellConfig(val enabled: Boolean = false, val allowedExecutables: Set<String> = emptySet())
```
---
## minimum test targets
### `LlamaCppInferenceProviderTest`
- request maps `GenerationConfig` fields correctly to `ChatCompletionRequest`
- usage mapped to `TokenUsage` correctly
- non-2xx response → `InferenceProviderException` thrown
- `healthCheck()` returns `Healthy` on 200, `Unhealthy` otherwise
### `DefaultModelManagerTest`
- `load()` same `modelId` twice → no-op, no process restart
- `load()` different `modelId` → kills current process, starts new
- health check timeout → `ModelLoadException`, no `ModelLoadedEvent` emitted
- `ModelLoadedEvent` emitted only after health check passes
- `ModelUnloadedEvent` emitted only after process is dead
### `ManagedInferenceProviderTest`
- `EPHEMERAL`: `unload()` called after `infer()` completes
- `DYNAMIC`: idle timer resets on each `infer()` call
- `PERSISTENT`: `unload()` never called automatically
### `ShellToolTest`
- empty `allowedExecutables` → all commands denied
- executable not in allowlist → `Failure` before process spawn
- timeout → process killed, `Failure` returned
- non-zero exit → `Failure` with stderr as reason
- environment is cleared — no parent env inheritance
### `FilesystemToolTest`
- empty `allowedPaths` → all paths denied
- path traversal `../` → blocked after `toRealPath()` resolution
- symlink escape → blocked after re-validation
- `affectedPaths()` returns empty set for `read`/`list`/`exists`
### `SandboxedToolExecutorTest`
- approval rejected → `ToolExecutionRejectedEvent` emitted, delegate not called
- delegate returns `Failure` → `.bak` restored, working dir deleted
- backup creation fails → `Failure` before delegate call
- `.bak` files cleaned up on success
---
## explicitly out of scope
- postgres
- ollama / vllm
- docker tools
- network tools
- telemetry exporters
- process namespace isolation
- CLI / API wiring (epic 12)
- config file loading (epic 12)
+416
View File
@@ -0,0 +1,416 @@
# Epic 12 — Technical Debt & Gap Closure
**status:** planned
**scope:** fix all known bugs, ambiguities, and structural gaps before building interfaces
**goal:** codebase is correct, documented, and stable before Epic 13 (interfaces) begins
---
## task 1: ModelDescriptor.capabilities
**problem:** `LlamaCppInferenceProvider.capabilities()` derives capabilities from model name string matching. fragile, wrong on rename, invisible failure.
**fix:** add `capabilities: Set<CapabilityScore>` to `ModelDescriptor`. `LlamaCppInferenceProvider.capabilities()` returns `descriptor.capabilities` directly.
**files:**
- `infrastructure/inference/.../ModelDescriptor.kt` — add field
- `infrastructure/inference/.../LlamaCppInferenceProvider.kt` — remove name matching, return descriptor field
**acceptance criteria:**
- capabilities declared per-descriptor, not derived from name
- no string matching logic in `capabilities()`
---
## task 2: DefaultInferenceRouter
**problem:** `InferenceRouter` interface exists, no implementation. orchestrator calls `route(stageId, emptySet())` with hardcoded empty capabilities.
**fix:**
```kotlin
class DefaultInferenceRouter(
private val registry: ProviderRegistry,
private val strategy: RoutingStrategy,
) : InferenceRouter {
override fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider {
val candidates = requiredCapabilities
.flatMap { registry.resolve(it) }
.distinctBy { it.id }
.ifEmpty { registry.listAll() }
return strategy.select(candidates, requiredCapabilities)
}
}
```
**files:**
- `core/inference/.../DefaultInferenceRouter.kt` — new
- `infrastructure/.../InfrastructureModule.kt` — wire `createInferenceRouter()`
**acceptance criteria:**
- routes by capability, not hardcoded
- `NoEligibleProviderException` on no candidates
- contract tests: match found, no match throws, empty capabilities returns any available
---
## task 3: StageConfig in WorkflowGraph
**problem:** `WorkflowGraph` holds `stages: Set<StageId>` only. orchestrator hardcodes context budget, generation config, capabilities.
**fix:**
```kotlin
data class StageConfig(
val requiredCapabilities: Set<ModelCapability> = emptySet(),
val tokenBudget: Int = 4096,
val generationConfig: GenerationConfig = GenerationConfig(),
val allowedTools: Set<String> = emptySet(),
val maxRetries: Int = 3,
)
data class WorkflowGraph(
val stages: Map<StageId, StageConfig>,
val transitions: Set<TransitionEdge>,
val start: StageId,
) {
val stageIds: Set<StageId> get() = stages.keys
init { require(start in stages) { "start stage must exist in stages" } }
}
```
**before dispatching:** run `grep -r "WorkflowGraph" --include="*.kt" -l` to know blast radius.
**files:**
- `core/transitions/.../StageConfig.kt` — new
- `core/transitions/.../WorkflowGraph.kt` — replace `Set<StageId>` with `Map<StageId, StageConfig>`
- `core/kernel/.../DefaultSessionOrchestrator.kt` — use stage config values
- all tests constructing `WorkflowGraph` — update constructor
**acceptance criteria:**
- orchestrator derives capabilities, budget, generationConfig from stage config
- existing transition resolution and cycle analysis tests pass
- no hardcoded values remain in orchestrator for stage-specific config
---
## task 4: core:risk module
**problem:** approval tier hardcoded to `T2`. no risk signal aggregation exists.
**new module:** `:core:risk`
**depends on:** `:core:events`, `:core:approvals`, `:core:validation`, `:core:transitions`
**types:**
```kotlin
enum class RiskLevel { LOW, MEDIUM, HIGH, CRITICAL }
enum class RiskAction { PROCEED, PROMPT_USER, BLOCK }
sealed class RiskSignal {
data class DestructiveOperation(val tier: Tier) : RiskSignal()
data class CycleWithoutExit(val cycleId: String) : RiskSignal()
data class RepeatedFailure(val reason: String, val count: Int) : RiskSignal()
data class ValidationErrors(val errorCount: Int) : RiskSignal()
data class InferenceTimeout(val elapsedMs: Long) : RiskSignal()
}
data class RiskSummary(
val level: RiskLevel,
val signals: List<RiskSignal>,
val recommendedAction: RiskAction,
)
fun RiskLevel.toApprovalTier(): Tier = when (this) {
RiskLevel.LOW -> Tier.T1
RiskLevel.MEDIUM -> Tier.T2
RiskLevel.HIGH -> Tier.T3
RiskLevel.CRITICAL -> Tier.T4
}
interface RiskAssessor {
fun assess(
report: ValidationReport,
outcome: StageOutcome,
state: OrchestrationState,
): RiskSummary
}
```
**DefaultRiskAssessor rules:**
- T3/T4 tool in outcome → `DestructiveOperation` → at least HIGH
- validation report has errors → `ValidationErrors` → at least MEDIUM
- `retryCount >= maxAttempts - 1``RepeatedFailure` → HIGH
- cycle without policy binding → `CycleWithoutExit` → MEDIUM
- recent `InferenceTimeoutEvent``InferenceTimeout` → MEDIUM
- no signals → LOW, PROCEED
**new event:** `RiskAssessedEvent(sessionId, stageId, riskSummaryId, level, action)` — register in `Serialization.kt`.
**files:**
- `core/risk/src/main/kotlin/.../` — all risk types + interface + default impl
- `core/events/.../RiskAssessedEvent.kt` — new
- `core/events/.../serialization/Serialization.kt` — register
- `core/kernel/.../DefaultSessionOrchestrator.kt` — replace hardcoded `Tier.T2`
**acceptance criteria:**
- `DefaultRiskAssessor` is pure (no IO, no coroutines)
- each signal type has a dedicated test
- `RiskAssessedEvent` serializable and registered
- orchestrator derives tier from risk summary
---
## task 5: stage timeout enforcement
**problem:** `OrchestrationConfig.stageTimeoutMs` configured but never enforced.
**fix:** wrap stage execution in `withTimeout(config.stageTimeoutMs)` in `DefaultSessionOrchestrator`. on `TimeoutCancellationException` emit `InferenceTimeoutEvent`, return `StageOutcome.InferenceFailure(retryable = true)`.
**files:**
- `core/kernel/.../DefaultSessionOrchestrator.kt`
**acceptance criteria:**
- stage exceeding timeout produces `InferenceFailure`
- `InferenceTimeoutEvent` emitted
- timeout is per-stage
---
## task 6: session type safety and FSM gaps
**problems:**
1. `DefaultSessionRepository.getSession(sessionId: String)` takes raw `String` — loses type safety
2. `SessionStatus.REPLAYED` has no documented FSM transitions — can sessions leave REPLAYED state?
3. `SessionEventMapper` may be dead code after Epic 3 replaced it with `SessionReducer`
**fixes:**
1. change signature to `getSession(sessionId: SessionId)`
2. define explicit FSM edges for REPLAYED — either terminal (no outgoing) or document valid transitions
3. grep codebase for `SessionEventMapper` usages — delete if unused, document if kept
**files:**
- `core/sessions/.../DefaultSessionRepository.kt`
- `core/sessions/.../SessionFsm.kt` (or equivalent)
**acceptance criteria:**
- `getSession` takes `SessionId`
- REPLAYED state transitions are explicit and tested
- `SessionEventMapper` either removed or documented with justification
---
## task 7: transition engine undefined behaviors
**problems:**
1. no-match behavior undefined — what happens when no transition matches current stage?
2. `StageId` origin unspecified — who generates it, orchestrator or transition engine?
**fixes:**
1. define explicit behavior: emit `StageFailedEvent` with reason `"no matching transition"`, return typed failure from resolver
2. document and enforce: `StageId` is always caller-generated (orchestrator). transition engine never creates `StageId` values.
**files:**
- `core/transitions/.../DefaultTransitionResolver.kt` — add no-match handling
- `core/transitions/.../TransitionDecision.kt` — add `NoMatch` case if not present
- CLAUDE.md — add StageId ownership rule
**acceptance criteria:**
- no-match produces deterministic typed failure, not silent null/hang
- test: resolver with no matching transition returns `NoMatch` decision
- `StageId` ownership documented
---
## task 8: CycleSignature edge inclusion
**problem:** `CycleSignature` is derived from node set only. two cycles with same nodes but different edge order are treated as identical. policy binding can silently apply to the wrong cycle.
**fix:** include normalized edge set in `CycleSignature`:
```kotlin
data class CycleSignature(
val nodes: SortedSet<StageId>,
val edges: SortedSet<Pair<StageId, StageId>>,
)
```
**files:**
- `core/transitions/.../CycleSignature.kt`
- `core/validation/.../SemanticValidator.kt` — update signature derivation
- affected tests
**acceptance criteria:**
- two cycles with same nodes but different edges produce different signatures
- existing single-cycle tests still pass
---
## task 9: Epic 5 resolution document
**problem:** Epic 5 (Approvals) has no resolution document. `ApprovalEngine` contract, tier evaluation logic, grant semantics, and `ApprovalMode` behavior are undocumented. Epic 13 (interfaces) builds approval interaction on top of this.
**fix:** write `docs/epics/epic-5-resolution.md` covering:
- `ApprovalEngine` interface contract
- tier evaluation logic per mode (PROMPT, AUTO, DENY, YOLO)
- grant extraction semantics from event stream
- `ApprovalMode.PROMPT` current behavior (evaluates existing grants, no interactive prompt yet)
- what's deferred to Epic 13
**files:**
- `docs/epics/epic-5-resolution.md` — new
**acceptance criteria:**
- document exists and covers all four approval modes
- current PROMPT behavior limitation explicitly stated
- Epic 13 approval interaction scope is clear
---
## task 10: artifact lifecycle enforcement
**problems:**
1. `ArtifactLifecyclePhase` transitions are undocumented and unenforced — no reducer or FSM
2. `ArtifactRelationshipAddedEvent.relationshipType: String` — typo produces silent invalid relationship
3. `validationResultIds: List<String>` type unpinned — unclear if these are `ValidationReportId` or something else
**fixes:**
1. introduce `ArtifactReducer` enforcing valid phase transitions. invalid transitions → `IllegalStateException` or typed error
2. validate `relationshipType` against `ArtifactRelationshipType.entries` at event creation
3. pin type: rename to `validationReportIds: List<ValidationReportId>` — breaking change, fix all usages
**files:**
- `core/artifacts/.../ArtifactReducer.kt` — new
- `core/artifacts/.../ArtifactProjector.kt` — wire reducer
- `core/events/.../ArtifactRelationshipAddedEvent.kt` — add validation
- `core/artifacts/.../ArtifactLineage.kt` — rename + retype field
**acceptance criteria:**
- invalid phase transition throws at reducer level
- all valid transitions tested
- `relationshipType` validated at event creation, test: invalid type throws
- `validationReportIds` typed as `List<ValidationReportId>`
---
## task 11: context engine edge cases
**problems:**
1. `buildingInProgress` stuck permanently if process crashes between `ContextBuildingStartedEvent` and completion
2. L0+L1 exceeding `TokenBudget.limit` has no defined overflow behavior
3. Conversation compression strategy "last N" — N is undocumented and presumably hardcoded
**fixes:**
1. add recovery: on replay, if `buildingInProgress = true` and no completion/failure event follows, treat as failed. add `ContextBuildingInterruptedEvent` or handle via timeout during rebuild.
2. define overflow behavior explicitly: if L0+L1 > limit, truncate L1 from oldest first. L0 is never truncated. document this as a hard rule.
3. make N configurable in `CompressionStrategy.Conversation(keepLast: Int = 10)`. document default.
**files:**
- `core/context/.../DefaultContextReducer.kt` — stuck state recovery
- `core/context/.../DefaultContextCompressor.kt` — L1 overflow handling
- `core/context/.../CompressionStrategy.kt` — add `keepLast` param
**acceptance criteria:**
- replaying a session with interrupted context build produces valid (failed) state
- L0+L1 overflow test: L1 truncated, L0 retained
- `keepLast` documented and tested
---
## task 12: inference contract gaps
**problems:**
1. `InferenceTimeout` and `InferenceCancellationToken` interaction undefined — potential double-cancellation race
2. stale provider snapshot: provider becomes unavailable between routing and `infer()` call
3. `CancellationException` swallow not enforced by contract test — any provider can silently break cooperative cancellation
**fixes:**
1. document interaction rule: timeout fires → cancels token → provider sees cancellation. token cancel does NOT fire timeout. add KDoc on both types.
2. add health check in `DefaultInferenceRouter.route()` — filter out `Unavailable` providers before selection. on `infer()` failure with `Unavailable` health, throw `ProviderUnavailableException` (not generic inference failure).
3. add contract test: cancel coroutine mid-inference, assert coroutine is actually cancelled within 500ms.
**files:**
- `core/inference/.../InferenceCancellationToken.kt` — KDoc
- `core/inference/.../InferenceTimeout.kt` — KDoc
- `core/inference/.../DefaultInferenceRouter.kt` — health check before selection
- `testing/contracts/.../InferenceProviderContractTest.kt` — cancellation enforcement test
**acceptance criteria:**
- timeout/token interaction documented
- unavailable provider filtered at routing, not discovered at inference time
- cancellation contract test fails if provider swallows `CancellationException`
---
## task 13: orchestrator ambiguities
**problems:**
1. `ReplayStrategy.SkipValidation` leaves artifacts stuck in `VALIDATING` phase permanently
2. `ValidationFailure.retryable` ownership unclear — validator sets it or orchestrator?
3. approval tier for validation-triggered approvals unspecified
**fixes:**
1. when `SkipValidation` active, emit synthetic `ArtifactValidatedEvent` for any artifact in `VALIDATING` phase. document this as replay-only behavior.
2. rule: validator sets `retryable` based on failure type. orchestrator only reads it, never overrides. document this ownership in KDoc on `ValidationFailure`.
3. validation-triggered approvals use `Tier.T2` explicitly. document in approval trigger logic. revisit when risk model is wired (task 4 feeds into this).
**files:**
- `core/kernel/.../ReplayOrchestrator.kt` — synthetic validation events
- `core/kernel/.../StageOutcome.kt` — KDoc on `ValidationFailure.retryable`
- `core/validation/.../ApprovalTrigger.kt` — explicit tier for validation approvals
**acceptance criteria:**
- replay with SkipValidation: no artifacts left in VALIDATING phase
- `retryable` ownership documented and tested
- validation approval tier explicit and documented
---
## task 14: tool execution gaps
**problems:**
1. `ToolResult.Success` has no `exitCode` field — shell tool exit codes lost in receipt
2. `affectedEntities` hardcoded to `emptyList()` in `SandboxedToolExecutor` — tool lineage permanently lost
**fixes:**
1. add `exitCode: Int = 0` to `ToolResult.Success`. `SandboxedToolExecutor.emitCompleted()` uses it. `ShellTool` sets actual exit code.
2. `SandboxedToolExecutor.emitCompleted()`: if tool implements `FileAffectingTool`, populate `affectedEntities` from `tool.affectedPaths(request)`.
**files:**
- `core/tools/.../ToolResult.kt` — add `exitCode`
- `infrastructure/tools/.../SandboxedToolExecutor.kt` — use exitCode + affectedPaths
- `infrastructure/tools/shell/.../ShellTool.kt` — set exitCode in result
- affected tests
**acceptance criteria:**
- shell tool non-zero exit captured in receipt
- file-affecting tools produce non-empty `affectedEntities`
- existing tool tests updated for new `ToolResult.Success` signature
---
## sequencing
```
task 1 (ModelDescriptor.capabilities) ← quick, unblocks task 2
task 2 (DefaultInferenceRouter) ← depends on task 1
task 3 (StageConfig) ← independent, high blast radius
task 4 (core:risk) ← depends on task 3
task 5 (stage timeout) ← depends on task 4
task 6 (session type safety + FSM) ← independent
task 7 (transition undefined behaviors) ← independent
task 8 (CycleSignature edges) ← independent
task 9 (Epic 5 resolution doc) ← independent, no code
task 10 (artifact lifecycle) ← independent
task 11 (context engine edge cases) ← independent
task 12 (inference contract gaps) ← depends on task 2
task 13 (orchestrator ambiguities) ← depends on task 4
task 14 (tool execution gaps) ← independent
```
tasks 611 and 14 can be parallelized if using subagents.
tasks 1→2→12 and 3→4→5→13 are the two critical chains.
---
## after this epic
- update `des-doc-v0.1.md` and `spec-v0.1.md` to reflect current architecture
- write module specs for: infrastructure tools, inference, sessions (impl-level), kernel
- then Epic 13: interfaces (TUI + CLI + Ktor server)
+280
View File
@@ -0,0 +1,280 @@
# Epic 2 — Session Lifecycle (FSM + Projection Layer)
## completed deliverables
### 1. session state model (projection output)
implemented a deterministic session state representation derived entirely from event streams.
final structure:
* `SessionState` (pure projection output)
* `SessionStatus`
* `SessionEvent` (FSM input domain)
key characteristics:
* immutable
* replay-safe
* derived only from events
* no identity leakage
session identity explicitly removed from projection state.
---
### 2. session identity boundary separation
introduced explicit separation between identity and state.
final model:
* `Session` → identity wrapper
* `SessionState` → projection result
structure:
```text id="s1"
Session(sessionId)
SessionState
```
this removed previous anti-pattern:
* embedding sessionId inside projection state
* late-binding identity during replay fold
---
### 3. session FSM (deterministic lifecycle rules)
implemented deterministic finite state machine governing session lifecycle transitions.
states:
* `CREATED`
* `ACTIVE`
* `PAUSED`
* `COMPLETED`
* `FAILED`
* `REPLAYED` (diagnostic)
properties:
* deterministic transitions
* no side effects
* replay-safe behavior
* strictly event-driven evaluation
FSM is pure function:
```text id="s2"
(SessionStatus, SessionEvent) → SessionStatus
```
---
### 4. session event interpretation layer
introduced mapping layer between persisted events and domain FSM events.
components:
* `SessionEventMapper`
responsibilities:
* convert `StoredEvent → SessionEvent`
* isolate domain semantics from persistence format
properties:
* no state mutation
* no replay logic
* stateless transformation only
---
### 5. session projection engine
implemented `SessionProjector` as domain-specific projection over generic replay infrastructure.
responsibilities:
* consumes ordered event stream
* applies FSM transitions
* produces deterministic session state
* tracks lifecycle metadata
properties:
* pure fold reducer
* stateless across executions
* deterministic replay behavior
state evolution:
```text id="s3"
StoredEvent → SessionEvent → FSM → SessionState
```
---
### 6. projection infrastructure alignment
aligned session model with generic replay engine (`:core:events`).
final integration:
* `Projection<S>` reused as abstraction boundary
* `EventReplayer<S>` drives deterministic reconstruction
* `SessionProjector` plugs into generic replay system
no duplication of replay logic inside sessions.
---
### 7. session repository (identity boundary)
introduced repository as thin orchestration facade over replay engine.
final structure:
```kotlin id="s4"
class DefaultSessionRepository(
private val replayer: EventReplayer<SessionState>
) {
fun getSession(sessionId: String): Session =
Session(
sessionId = sessionId,
state = replayer.rebuild(sessionId)
)
}
```
responsibilities:
* binds session identity to replay result
* no projection logic
* no FSM logic
* no event interpretation
---
### 8. replay integration
session lifecycle fully integrated into event-sourced replay pipeline.
final flow:
```text id="s5"
EventStore
EventReplayer<SessionState>
SessionProjector
SessionState
Session (identity wrapper)
```
system guarantees:
* full replay determinism
* identical event streams → identical session states
* ordering enforced by store layer
---
### 9. test coverage completed
implemented deterministic coverage across session lifecycle:
* session reconstruction from event stream
* FSM transition correctness
* invalid transition detection
* replay determinism guarantees
* empty stream handling
* multi-event lifecycle correctness
introduced fixtures:
* `stored()` event builder for deterministic event creation
aligned contract tests:
* projection contract compliance
* replay contract validation
* event store contract enforcement
---
### 10. architectural cleanup (refactor milestone)
removed legacy session coupling patterns:
* sessionId inside `SessionState`
* implicit state hydration logic
* repository-level replay duplication
* ambiguous projection initialization patterns
removed or deprecated:
* `SessionCounterProjection` as state-embedded counter logic (moved toward event-derived metrics approach)
* FSM leakage into repository layer
---
# final architecture after Epic 2
```text id="s6"
EventStore
EventReplayer<SessionState>
SessionProjector
SessionState
Session (identity wrapper)
```
---
# major architectural outcomes
Epic 2 established:
* strict separation between identity and state
* deterministic FSM-driven session lifecycle
* event-sourced state reconstruction model
* reusable generic replay engine (`:core:events`)
* domain-specific projection layer (`:core:sessions`)
* repository as pure boundary adapter
* fully replay-safe session lifecycle semantics
---
# what Epic 2 intentionally does NOT include
not implemented:
* workflow graph execution
* transition DSL
* orchestration runtime
* stage execution engine
* scheduling or async coordination
* tool execution or kernel integration
those are explicitly deferred to Epic 3+
---
# final state
Correx now has:
> a deterministic, event-sourced session lifecycle system built on a generic replay engine, with strict separation between identity, state, and event interpretation, fully replay-safe and FSM-driven.
+203
View File
@@ -0,0 +1,203 @@
# Epic 2: Session Lifecycle (Finite State Machine + Projection Layer)
**status:** proposed
**date:** 08.05.2026 (May)
**scope:** `:core:sessions` (depends on `:core:events`)
---
## context
With a stable event log (Epic 1 complete), Correx now needs a structured runtime abstraction that turns raw events into meaningful execution units.
A session is the primary unit of work in the system:
* it represents a single interactive or automated workflow
* it evolves over time through events
* it must be reconstructible from the event log (replayable state)
Without a session layer, events remain unstructured history with no execution semantics.
---
## goal
Introduce a **deterministic session lifecycle system** built on top of events:
> a finite state machine + projection model that reconstructs session state from the event stream
---
## scope (what IS included)
### 1. session model
Define `Session` as a projection, not stored state:
* `sessionId`
* `status`
* `createdAt`
* `updatedAt`
* derived metadata (optional: stage, counters, flags)
Session state is **derived from events only**, never mutated directly.
---
### 2. session states (FSM)
Define explicit lifecycle states:
* `CREATED`
* `ACTIVE`
* `PAUSED`
* `COMPLETED`
* `FAILED`
* `REPLAYED` (optional diagnostic state)
Transitions are strictly event-driven.
---
### 3. session events (from Epic 1)
Introduce interpretation layer over existing events:
* `SessionStarted`
* `SessionPaused`
* `SessionResumed`
* `SessionCompleted`
* `SessionFailed`
These are stored in `:core:events`, not duplicated.
---
### 4. session projection builder
Implement:
> `SessionProjector`
Responsibilities:
* consumes ordered `EventEnvelope` stream
* rebuilds current session state
* applies deterministic reduction logic
Rules:
* pure function behavior (events → state)
* no side effects
* replay-safe
---
### 5. transition rules (FSM logic)
Define strict rules:
* invalid transitions must be rejected during projection validation
* transitions are derived from event sequences, not external mutation
* last valid state wins (replay consistency rule)
Example:
* `CREATED → ACTIVE` allowed
* `ACTIVE → COMPLETED` allowed
* `COMPLETED → ACTIVE` invalid (ignored or flagged)
---
### 6. session repository
Provide abstraction:
* `getSession(sessionId): Session`
* `getAllSessions()`
* `rebuild(sessionId)` (replay from events)
Backed entirely by `EventStore`.
---
### 7. minimal runtime behavior (no orchestration yet)
Session layer only:
* no kernel execution
* no tool execution
* no transitions engine
* no approvals
It is purely **state interpretation over events**
---
### 8. test coverage
* session reconstruction from event stream
* invalid transition detection
* replay determinism (same events → same session state)
* multi-event lifecycle correctness
* empty session handling
---
## explicit exclusions
Epic 2 does NOT include:
* workflow graph / transitions engine (`:core:transitions`)
* approval system (`:core:approvals`)
* tool execution (`:core:tools`)
* inference or routing
* context compression
* orchestration kernel
---
## consequences
### positive
* introduces first meaningful domain abstraction over events
* enables reasoning about execution lifecycle
* provides foundation for orchestration layer
* makes replay human-interpretable (not just raw logs)
* isolates state interpretation logic from persistence
### negative
* introduces FSM complexity early in system lifecycle
* requires strict discipline to avoid state mutation leaks
* projection correctness becomes critical dependency for all higher layers
* may require refactoring if event types evolve significantly
---
## rationale
Events alone are insufficient for system reasoning.
A session layer is required to:
> translate raw event history into structured execution semantics
This is the first step from:
* “log of facts”
to
* “interpretable system state”
---
## status
Epic 2 becomes valid only after:
* Epic 1 is complete ✔
* EventStore contract is stable ✔
* replay guarantees are verified ✔
It is the **first domain layer built on top of the event system**, and the first step toward orchestration.
+256
View File
@@ -0,0 +1,256 @@
# Epic 3 — Transition Engine
## completed deliverables
### 1. workflow graph model
implemented deterministic workflow topology representation.
core structures:
* `WorkflowGraph`
* `TransitionEdge`
* `StageId`
* `TransitionId`
* `TransitionCondition`
* `EvaluationContext`
graph characteristics:
* immutable
* deterministic
* replay-safe
* runtime-agnostic
---
## 2. deterministic transition resolution
implemented pure transition evaluation engine.
properties:
* evaluates only outgoing edges from current stage
* deterministic ordering
* sequential evaluation only
* first matching transition wins
* stable replay behavior
core components:
* `DefaultTransitionResolver`
* `TransitionConditionEvaluator`
* `TransitionDecision`
---
## 3. cycle analysis infrastructure
implemented read-only graph cycle analysis.
implemented:
* deterministic adjacency construction
* DFS-based traversal
* cycle extraction
* cycle canonicalization
* duplicate elimination
important:
* analysis only
* no runtime execution semantics yet
* no scheduler/orchestrator coupling
architectural rule:
* cycles are structurally allowed
* behavior constraints belong to future policy/runtime layers
---
## 4. stage execution contract
defined strict execution boundary between:
* transition engine
* runtime execution
implemented concepts:
* stage execution request
* stage execution result
* deterministic execution semantics
* execution/event separation
important:
* transitions engine does NOT execute runtime logic
* transitions engine does NOT orchestrate scheduling
---
## 5. transition event model
added workflow execution events into event system.
implemented events:
* `StageStartedEvent`
* `StageCompletedEvent`
* `StageFailedEvent`
* `TransitionExecutedEvent`
properties:
* serializable
* append-only compatible
* replay-safe
* session-scoped
---
## 6. event-native session integration
reworked session architecture from:
* FSM + lossy mapping
into:
* deterministic event reduction
removed conceptual dependency on:
* collapsed transition semantics
* imperative FSM mutation
introduced:
* `SessionReducer`
* `DefaultSessionReducer`
session state now evolves via:
```text id="e1"
StoredEvent → SessionReducer → SessionState
```
instead of:
```text id="e2"
StoredEvent → Mapper → FSM → State
```
---
## 7. projection architecture alignment
aligned sessions with true event-sourcing semantics.
final rules:
* event stream is single source of truth
* projections are pure deterministic folds
* no hidden mutable state
* no runtime side effects
* replay reconstructs full state deterministically
---
## 8. replay integration
integrated transition execution into replay pipeline.
final replay flow:
```text id="e3"
Workflow execution
→ transition events
→ EventStore
→ replay
→ SessionProjector
→ SessionState
```
transition execution now becomes part of durable system history.
---
## 9. testing completed
implemented coverage for:
* reducer semantics
* deterministic replay
* projector lifecycle behavior
* transition resolution
* transition event serialization
* replay integration
removed obsolete tests:
* `SessionFsm`
* `SessionEventMapper`
* mapper-driven FSM semantics
---
# final architecture after Epic 3
```text id="e4"
WorkflowGraph
TransitionResolver
Stage Execution
Transition Events
Event Store
Replay / Projection
SessionState
```
---
# major architectural outcomes
Epic 3 established:
* deterministic workflow execution
* replay-safe orchestration semantics
* event-native state derivation
* strict separation of:
* execution
* transitions
* persistence
* projections
* runtime orchestration
---
# what Epic 3 intentionally does NOT include
not implemented yet:
* retry policies
* bounded runtime loop enforcement
* approval gating
* tool orchestration policies
* scheduling
* runtime budgeting
* adaptive routing logic
those belong to future epics.
---
# final state
Correx now has:
> a deterministic, event-sourced workflow transition engine with replay-consistent session projection and no hidden state mutation.
+297
View File
@@ -0,0 +1,297 @@
# Epic 3: Transition Engine (Deterministic Workflow Graph + Event-Driven Execution)
**status:** proposed
**date:** 09.05.2026 (May)
**scope:** `:core:transitions` (depends on `:core:events`, `:core:sessions`)
---
## context
With deterministic replay and session projection already implemented (Epic 2 complete), Correx now needs a workflow execution model capable of describing and evaluating structured agent pipelines.
A session lifecycle alone is insufficient for orchestration semantics.
The system now requires:
* workflow topology
* transition rules
* deterministic execution flow
* replay-safe workflow progression
Without a transition engine:
* sessions cannot evolve through structured workflows
* stage progression remains implicit
* orchestration semantics cannot be reconstructed from history
---
## goal
Introduce a **deterministic workflow transition engine**:
> a pure graph evaluation system that resolves workflow transitions and emits replay-safe execution events
The transition engine must remain:
* deterministic
* stateless
* replay-safe
* runtime-agnostic
It is NOT a scheduler or orchestration kernel.
---
## scope (what IS included)
### 1. workflow graph model
Define immutable workflow topology structures:
* `WorkflowGraph`
* `TransitionEdge`
* `StageId`
* `TransitionId`
Graph structure:
```kotlin
data class WorkflowGraph(
val stages: Set<StageId>,
val transitions: Set<TransitionEdge>,
val start: StageId
)
```
Properties:
* immutable
* deterministic
* runtime-independent
* replay-safe
---
### 2. deterministic transition evaluation
Implement deterministic transition resolution.
Core concepts:
* `TransitionCondition`
* `EvaluationContext`
* `TransitionConditionEvaluator`
* `TransitionDecision`
* `DefaultTransitionResolver`
Rules:
* evaluate only outgoing transitions from current stage
* deterministic ordering only
* sequential evaluation only
* first matching transition wins
* no implicit randomness or parallelism
Transition evaluation must behave identically under replay.
---
### 3. graph analysis and cycle detection
Implement read-only graph validation and analysis.
Includes:
* deterministic adjacency construction
* DFS-based traversal
* cycle extraction
* cycle canonicalization
* duplicate cycle elimination
* unreachable node detection
Important:
* cycles are structurally allowed
* cycle analysis is informational in Epic 3
* runtime loop policies are NOT part of this epic
Cycle handling remains deterministic and replay-safe.
---
### 4. stage execution contract
Define strict execution boundary between transition evaluation and runtime execution.
Introduce:
* stage execution request model
* stage execution result model
* deterministic execution semantics
Important:
* transition engine does NOT execute runtime logic
* transition engine does NOT schedule work
* transition engine does NOT manage concurrency
It only evaluates graph progression.
---
### 5. transition execution events
Introduce workflow execution events into the event system.
Implemented events:
* `StageStartedEvent`
* `StageCompletedEvent`
* `StageFailedEvent`
* `TransitionExecutedEvent`
Properties:
* append-only compatible
* serializable
* replay-safe
* deterministic
Workflow progression becomes part of durable event history.
---
### 6. event-native session integration
Integrate workflow execution into session state reconstruction.
Introduce:
* `SessionReducer`
* `DefaultSessionReducer`
Session state now evolves via deterministic event reduction:
```text
StoredEvent → SessionReducer → SessionState
```
instead of:
```text
StoredEvent → Mapper → FSM → State
```
Rules:
* transition engine never mutates session state directly
* events are the single source of truth
* projections derive state from full semantic event stream
* no lossy event collapsing is allowed
---
### 7. replay-safe projection architecture
Align projections with true event-sourcing semantics.
Projection rules:
* projections are pure deterministic folds
* no side effects
* no runtime clocks
* no mutable hidden state
* replay must reconstruct identical state from identical streams
Transition execution history becomes replayable system truth.
---
### 8. deterministic testing coverage
Implement coverage for:
* transition resolution determinism
* graph analysis correctness
* cycle extraction stability
* transition event serialization
* replay determinism
* reducer semantics
* session reconstruction from workflow events
End-to-end replay consistency is a required invariant.
---
## explicit exclusions
Epic 3 does NOT include:
* scheduling
* runtime orchestration kernel
* retry policies
* runtime loop enforcement
* approval gating
* tool execution policies
* adaptive routing logic
* budget enforcement
* concurrency control
* distributed execution
Those belong to future orchestration/runtime layers.
---
## consequences
### positive
* introduces deterministic workflow semantics
* makes workflow progression replayable
* formalizes orchestration topology
* separates workflow evaluation from runtime execution
* enables future policy/runtime layers
* preserves strict event-sourcing guarantees
---
### negative
* introduces graph complexity into core architecture
* deterministic guarantees constrain future runtime flexibility
* workflow semantics now depend heavily on projection correctness
* cycle handling will require future runtime policy enforcement
* event semantics become significantly richer and more difficult to evolve
---
## rationale
Sessions alone describe lifecycle state, but not workflow progression.
A transition engine is required to:
> translate workflow topology into deterministic event-driven execution semantics
This moves Correx from:
* “stateful session replay”
to
* “deterministic workflow execution replay”
Workflow behavior now becomes reconstructible from history.
---
## status
Epic 3 becomes valid only after:
* Epic 1 is complete ✔
* Epic 2 is complete ✔
* replay determinism is verified ✔
* session projection infrastructure exists ✔
It is the first orchestration-oriented layer in Correx and establishes the deterministic execution substrate required for future runtime and policy systems.
+181
View File
@@ -0,0 +1,181 @@
# Epic 4 — Validation Pipeline (summary)
## goal
deterministic, replay-safe validation layer over workflow execution model (graph + transitions + sessions + events), producing a structured report and optional approval request, without influencing execution.
---
## core idea
Epic 4 is a **pure analysis layer**:
* consumes: `ValidationContext` (graph + detected cycles + policies + session state)
* produces:
* `ValidationReport` (deterministic, hierarchical)
* optional `ApprovalRequest` (boundary artifact)
it never:
* executes workflows
* modifies state
* triggers transitions
* enforces runtime behavior
---
## module structure
```text
core/validation
├── model
├── graph
├── transition
├── session
├── semantic
├── pipeline
└── approval
```
---
## 1. model layer
defines shared validation contract:
* `ValidationReport` (sections-based hierarchy)
* `ValidationSection`
* `ValidationIssue`
* `ValidationSeverity`
* `ValidationContext`
context is a full snapshot:
* `WorkflowGraph`
* `DetectedCycle` (from Epic 3)
* `CyclePolicyBinding`
* `SessionState`
---
## 2. graph validation
checks structural correctness only:
* start node existence
* dangling transitions (invalid stage references)
* cycle reporting (passed-through from Epic 3, no interpretation)
cycles are:
* allowed
* informational only
---
## 3. transition validation
validates engine consistency:
* transition endpoints exist in graph
* condition presence / sanity
* deterministic ordering correctness per source node
no evaluation or execution of conditions.
---
## 4. session validation
validates projection consistency:
* temporal consistency (`createdAt <= updatedAt`)
* session state sanity (`invalidTransitions >= 0`)
* light consistency checks against graph context
no replay execution or event mutation logic.
---
## 5. semantic validation
configuration-level validation layer:
* cycle-policy binding completeness (mode B: required only in execution-capable mode)
* cross-layer consistency rules
* extension point for future domain rules
cycle identity is based on:
* `CycleSignature(nodes only, normalized)`
---
## 6. pipeline executor
deterministic orchestration:
* executes validators in fixed order
* aggregates `ValidationSection`
* fully replayable and stateless
```text
Graph → Transition → Session → Semantic → Report
```
no branching logic, no execution semantics.
---
## 7. approval trigger
boundary component:
* consumes `ValidationReport`
* computes `RiskSummary`
* produces optional `ApprovalRequest`
rules:
* errors or missing cycle policies trigger approval requirement
* does not execute or control workflow system
---
## final outputs
### validation output
```text
ValidationReport
├── graph
├── transition
├── session
└── semantic
```
### approval output
```text
ApprovalRequest? (optional)
```
---
## key invariants enforced
* deterministic execution (same input → same report)
* replay-safe evaluation
* no state mutation
* no execution coupling
* cycles allowed structurally
* policies required only as semantic governance, not structural correctness
---
## architectural position
Epic 4 is the **boundary correctness layer**:
> it ensures the system is internally consistent and safely configurable, without deciding how it runs.
+315
View File
@@ -0,0 +1,315 @@
# Epic 4: Validation Pipeline (Deterministic Analysis + Cross-Layer Consistency Engine)
**status:** completed
**date:** 09.05.2026 (May)
**scope:** `:core:validation` (depends on `:core:events`, `:core:sessions`, `:core:transitions`, Epic 3 analysis outputs)
---
## context
With deterministic event sourcing (Epic 1), session projection (Epic 2), and workflow transition semantics (Epic 3) in place, Correx now has a complete execution and reconstruction model.
However, there is no formal layer that ensures:
* graph integrity
* transition consistency
* session projection correctness
* configuration validity (cycle policies, semantic rules)
* cross-layer coherence under replay
Without a validation layer:
* invalid workflow graphs can still be constructed
* inconsistent transitions can pass unnoticed
* session projections can diverge semantically from graph structure
* cycle governance (policy binding) is not enforceably checked
* system correctness is only implicitly trusted, not verified
Epic 4 introduces a deterministic validation system to close this gap.
---
## goal
Introduce a **deterministic, replay-safe validation pipeline**:
> a pure analysis engine that validates workflow structure, execution projections, and configuration consistency without influencing execution behavior
The system must remain:
* deterministic
* stateless
* replay-safe
* non-executing
* side-effect free
It is NOT an orchestration engine and does NOT participate in runtime control flow.
---
## scope (what IS included)
---
### 1. validation model layer
Define unified validation representation:
* `ValidationReport`
* `ValidationSection`
* `ValidationIssue`
* `ValidationSeverity`
* `ValidationContext`
Validation context is a full system snapshot:
* `WorkflowGraph`
* `DetectedCycle` (Epic 3 output)
* `CyclePolicyBinding`
* `SessionState`
Properties:
* immutable
* replay-safe
* fully deterministic input contract
---
### 2. graph validation
Validate structural correctness of workflow topology.
Checks:
* start node existence
* dangling transitions (invalid stage references)
* structural consistency of graph definition
* cycle presence reporting (pass-through only)
Important:
* cycles are allowed by design (Epic 3)
* cycle detection is informational only
* no enforcement or interpretation of cycles occurs here
---
### 3. transition validation
Validate deterministic consistency of transition engine inputs.
Checks:
* transition endpoints exist in graph
* condition presence and structural validity
* deterministic ordering correctness per source node
* ambiguity detection in transition resolution ordering
Important:
* transition conditions are NOT executed
* no resolution logic is invoked
* no runtime decisioning occurs
---
### 4. session validation
Validate projection consistency against event-derived state.
Checks:
* temporal consistency (`createdAt ≤ updatedAt`)
* session state sanity (invalid transition counts)
* consistency between session state and graph structure
* lightweight anomaly detection over projection output
Important:
* no event replay is performed
* no projection recomputation occurs
* session state is treated as immutable derived artifact
---
### 5. semantic validation layer
Validate configuration and cross-domain consistency rules.
Includes:
* cycle-policy binding completeness (mode-dependent enforcement)
* configuration consistency checks
* cross-layer structural coherence rules
Cycle semantics:
* `CycleSignature` derived from normalized node sets
* edges are not part of identity
* policy binding is evaluated against cycle signatures only
Important:
* cycles remain structurally valid (Epic 3 rule preserved)
* semantic layer does NOT enforce execution behavior
* policies are treated as configuration constraints, not runtime logic
---
### 6. validation pipeline executor
Define deterministic orchestration of validation layers.
Pipeline behavior:
* strictly ordered execution
* no hidden parallelism
* fully deterministic evaluation order
* stateless execution model
Execution flow:
```text
Graph → Transition → Session → Semantic → ValidationReport
```
Important:
* pipeline aggregates results only
* no control flow decisions beyond aggregation
* no mutation of input context
---
### 7. approval trigger integration point
Introduce boundary layer between validation and external decision systems.
Responsibilities:
* evaluate `ValidationReport`
* compute risk summary
* decide whether approval request is required
* emit `ApprovalRequest` artifact
Important constraints:
* no execution coupling
* no workflow control logic
* no system orchestration responsibility
* strictly output transformation layer
Output artifacts:
* `ApprovalRequest`
* `RiskSummary`
Behavior:
* errors or missing cycle-policy bindings trigger approval requirement
* acts as deterministic gating boundary, not execution controller
---
### 8. deterministic testing requirements
Validation system must be fully testable under replay constraints.
Coverage includes:
* graph validation determinism
* transition validation consistency
* session projection sanity validation
* semantic rule enforcement consistency
* pipeline ordering determinism
* approval trigger stability
* full system replay equivalence
Invariant:
> identical input context must always produce identical ValidationReport and ApprovalRequest outcome
---
## explicit exclusions
Epic 4 does NOT include:
* workflow execution
* transition evaluation or resolution
* scheduling or orchestration
* runtime policy enforcement
* execution retry logic
* system control flow decisions
* async or distributed validation execution
* event mutation or emission
Those belong to future runtime/orchestration layers.
---
## consequences
### positive
* introduces explicit correctness boundary for entire system
* makes workflow + session consistency verifiable
* decouples analysis from execution
* enables safe pre-execution validation gating
* formalizes configuration governance (cycle policies)
* improves replay debugging and system observability
---
### negative
* adds another full abstraction layer over already complex system
* increases conceptual separation between “valid structure” and “valid execution”
* introduces potential duplication of logic if misused outside boundary
* requires strict discipline to avoid leaking execution semantics into validation
* makes system reasoning more layered (analysis vs execution vs policy)
---
## rationale
Epic 4 exists because deterministic execution alone is insufficient.
Even with:
* event sourcing (Epic 1)
* projection layer (Epic 2)
* workflow engine (Epic 3)
the system still lacks:
> a formal correctness boundary over structure, configuration, and derived state
This epic ensures:
* workflows are structurally valid before execution
* session state remains consistent with graph semantics
* configuration constraints (cycle policies) are explicitly governed
* system behavior remains fully replay-verifiable
It completes the shift from:
* “deterministic execution system”
to
* “deterministic + verifiable workflow system”
---
## status
Epic 4 is considered complete once:
* validation model is defined ✔
* graph/transition/session/semantic validators exist ✔
* pipeline executor is deterministic ✔
* approval trigger boundary is implemented ✔
* replay tests confirm stability ✔
It is the final analysis layer before runtime orchestration and execution-control systems are introduced in future epics.
+181
View File
@@ -0,0 +1,181 @@
# Epic 6 — Artifact System (Contract Layer + Lifecycle Events)
## completed deliverables
### 1. artifact identity
introduced `ArtifactId` as a type alias to `TypeId` in `core:events`, following the established identity type pattern.
* lives in `core:events` alongside `SessionId`, `StageId`, and other shared vocabulary types
* available to all modules that depend on `:core:events`
* no circular dependency risk
---
### 2. artifact lifecycle phase model
introduced `ArtifactLifecyclePhase` as a serializable enum in `core:events`.
phases (in mandatory transition order):
```text
CREATED → VALIDATING → VALIDATED → REJECTED
↘ SUPERSEDED → ARCHIVED
```
properties:
* all six phases are mandatory by spec
* lives in `core:events` so lifecycle events can reference it with full type safety
* corrections to artifacts produce new artifacts via supersession — not in-place edits
---
### 3. artifact contract layer
introduced the `Artifact` sealed interface as the root domain abstraction for all structured model outputs.
final structure:
```text
Artifact (sealed interface)
├── id (ArtifactId)
├── sessionId (SessionId)
├── stageId (StageId)
├── schemaVersion (Int)
├── createdAt (Instant)
├── lifecyclePhase (ArtifactLifecyclePhase)
├── lineage (ArtifactLineage)
└── metadata (Map<String, String>)
```
properties:
* immutable after creation
* serialization-safe via kotlinx.serialization
* no infrastructure coupling
* extensible — concrete artifact categories are deferred
---
### 4. artifact relationship model
introduced append-only relationship tracking between artifacts.
final structures:
* `ArtifactRelationshipType` — seven typed relationships:
* `PARENT`, `CHILD`, `SUPERSEDES`, `DERIVED_FROM`
* `VALIDATED_BY`, `APPROVED_BY`, `GENERATED_FROM`
* `ArtifactRelationship(sourceId, targetId, type)`
properties:
* relationships are append-only — never mutated or removed
* all relationship changes produce new events
---
### 5. artifact lineage model
introduced immutable lineage tracking for all artifacts.
final structure:
```text
ArtifactLineage
├── parentIds (List<ArtifactId>)
├── relationships (List<ArtifactRelationship>)
└── validationResultIds (List<String>)
```
properties:
* fully serializable
* validation results referenced by ID only — no direct dependency on `core:validation`
* replayable and traceable from event log alone
---
### 6. artifact serialization module
introduced `artifactModule: SerializersModule` in `core:artifacts`.
* polymorphic block scaffolded for concrete artifact subclasses (deferred)
* follows the same pattern as `eventModule` in `core:events`
---
### 7. artifact lifecycle events
introduced seven lifecycle events in `core:events`, covering the full artifact lifecycle and relationship tracking.
events:
* `ArtifactCreatedEvent(artifactId, sessionId, stageId, schemaVersion)`
* `ArtifactValidatingEvent(artifactId, sessionId, stageId)`
* `ArtifactValidatedEvent(artifactId, sessionId, stageId)`
* `ArtifactRejectedEvent(artifactId, sessionId, stageId, reason)`
* `ArtifactSupersededEvent(artifactId, supersededById, sessionId, stageId)`
* `ArtifactArchivedEvent(artifactId, sessionId, stageId)`
* `ArtifactRelationshipAddedEvent(sourceId, targetId, relationshipType, sessionId)`
properties:
* all registered in `eventModule` polymorphic block
* `relationshipType` carried as `String` to avoid circular dependency with `core:artifacts`
* serialization-safe, replay-ready
---
# final architecture after Epic 6
```text
Artifact (sealed interface — root abstraction)
ArtifactLifecyclePhase (CREATED → ... → ARCHIVED)
ArtifactLineage (parentIds + relationships + validationResultIds)
ArtifactRelationship (append-only, 7 typed relationships)
Lifecycle events in core:events (7 events, all registered)
artifactModule (SerializersModule, ready for subclass registration)
```
---
# major architectural outcomes
Epic 6 established:
* type-safe artifact identity integrated into the shared event vocabulary
* immutable, replayable artifact contract with full lifecycle phase tracking
* append-only relationship graph with seven typed relationship kinds
* lineage model decoupled from validation internals (reference by ID only)
* lifecycle event backbone enabling state reconstruction from events alone
* serialization module scaffolded for concrete artifact categories
---
# what Epic 6 intentionally does NOT include
not implemented:
* concrete artifact categories (`ReasoningArtifact`, `PatchArtifact`, `PlanArtifact`, etc.)
* artifact reducer, projector, or repository (state reconstruction layer)
* persistence — artifacts are not stored independently; state rebuilds from events
* provenance metadata model (originating model, provider, generation config, tool receipts)
* semantic validation — artifact legality is validated externally by `:core:validation`
* approval integration
those are explicitly deferred to later epics.
---
# final state
Correx now has:
> an immutable, event-sourced artifact contract layer with typed identity, a mandatory six-phase lifecycle, append-only relationship tracking, and a full suite of lifecycle events — establishing the structured output abstraction through which models contribute semantic work to the system, with state fully reconstructable from the event log.
+203
View File
@@ -0,0 +1,203 @@
# Epic 7 — Context Engine (Deterministic Context Synthesis Layer)
## completed deliverables
### 1. context identity
introduced `ContextPackId` and `ContextEntryId` as type aliases to `TypeId` in `core:events`.
* lives alongside other shared vocabulary types (`SessionId`, `ArtifactId`, etc.)
* available to all modules depending on `:core:events`
---
### 2. context lifecycle events
introduced five context lifecycle events in `core:events`, covering the full pack building pipeline.
events:
* `ContextBuildingStartedEvent(sessionId, stageId)`
* `ContextPackBuiltEvent(contextPackId, sessionId, stageId, budgetUsed, budgetLimit)`
* `CompressionAppliedEvent(contextPackId, layer, entriesRemoved, strategyApplied)`
* `LayerTruncatedEvent(contextPackId, layer, entriesDropped, reason)`
* `ContextBuildingFailedEvent(sessionId, stageId, reason)`
properties:
* all registered in `eventModule` polymorphic block
* emitted during pack assembly — enables deterministic replay of compression decisions
* `layer` and `strategyApplied` carried as `String` — avoids circular dependency with `core:context`
---
### 3. context layer model
introduced `ContextLayer` as a serializable enum with five distinct layers.
```text
L0 live execution [active request, current tool output, pending approval]
L1 stage-local context [artifacts and receipts from the current stage]
L2 compressed session [summaries of completed stages; kept under a token cap]
L3 project memory [persistent facts, architecture decisions, key outputs]
L4 archival history [full event log; not used for inference]
```
models receive a `ContextPack` built from L0L2; L3 optionally injected if relevant. L4 is archival only.
---
### 4. context pack contract types
introduced the core domain types for context synthesis.
final structures:
* `ContextEntry(id, layer, content, sourceType, sourceId, tokenEstimate)` — typed reference to an event, artifact excerpt, summary, or policy snippet
* `CompressionMetadata(appliedStrategies, truncatedLayers, entriesDropped)` — audit trail of compression decisions
* `ContextPack(id, sessionId, stageId, layers, budgetUsed, budgetLimit, compressionMetadata)` — the final assembled context artifact
* `TokenBudget(limit, used)` — transient computation value with `remaining`, `consume()`, and `canFit()` helpers; intentionally not serializable
---
### 5. compression pipeline
introduced a content-type-specific, rule-based, deterministic compression pipeline. zero model calls.
final structure:
* `CompressionStrategy` (sealed interface, five content-type-specific implementations):
* `ToolLog` — deduplicate identical content, drop oldest when over budget
* `Conversation` — keep last N verbatim, drop oldest first under budget pressure
* `Artifact` — keep most recent entry per `sourceId`
* `SteeringNote` — always retained verbatim, never dropped
* `EventHistory` — always retained; already-compressed DecisionPoints from prior stages
* `ContextCompressor` (interface)
* `DefaultContextCompressor` — applies strategies deterministically; drop order: L2 first, then L1; L0 is never dropped
key invariant:
> same input always produces the same output — compression is replay-safe
---
### 6. context state and projection
introduced the standard event-sourced pattern for context state.
final structures:
* `ContextState(builtPackIds, buildingInProgress)` — fully rebuildable from events; no external data
* `ContextReducer` (interface)
* `DefaultContextReducer` — handles `ContextBuildingStartedEvent`, `ContextPackBuiltEvent`, `ContextBuildingFailedEvent`; passes all others through unchanged
* `ContextProjector` — implements `Projection<ContextState>`; delegates to reducer
---
### 7. context pack builder
introduced a pure, side-effect-free context pack assembly pipeline.
final structures:
* `ContextPackBuilder` (interface)
* `DefaultContextPackBuilder` — compresses entries, groups by layer, tracks budget usage and truncation metadata
key invariant enforced:
> entries with `sourceType` of `"steeringNote"` or `"eventHistory"` are partitioned before compression and always retained, regardless of budget pressure
* `DecisionPointBuilder` (interface)
* `DefaultDecisionPointBuilder` — extracts only L0 and L1 entries from a pack for model inference calls
---
### 8. context repository
introduced `DefaultContextRepository` over `EventReplayer<ContextState>`, following the established repository pattern.
* single method: `getContextState(sessionId): ContextState`
* delegates to `replayer.rebuild(sessionId)`
* no business logic — thin wiring layer
---
### 9. context serialization module
introduced `contextModule: SerializersModule` in `core:context`.
* polymorphic block scaffolded for future extension
* follows the same pattern as `eventModule` and `artifactModule`
---
### 10. test coverage
implemented deterministic and projection tests covering all core behaviors.
deterministic tests (27):
* compression strategies: determinism, layer drop order, never-drop invariants (SteeringNote, EventHistory), artifact deduplication, tool log deduplication
* token budget: arithmetic correctness, immutability, exhaustion, canFit semantics
* pack builder: layer grouping, budget tracking, budget enforcement, decision point filtering, steering note retention under budget pressure, empty entries
projection tests (22):
* reducer: all three context event transitions, pass-through for unrelated events
* projector: initial state, deterministic replay
---
# final architecture after Epic 7
```text
ContextBuildingStartedEvent → ContextPackBuiltEvent (via core:events)
ContextState (rebuilt from events via DefaultContextReducer + ContextProjector)
DefaultContextPackBuilder (pure function)
├── pins: SteeringNote + EventHistory entries (never dropped)
├── compresses: remaining entries via DefaultContextCompressor
│ └── strategy dispatch: ToolLog / Conversation / Artifact / SteeringNote / EventHistory
│ └── drop order: L2 → L1 (L0 never dropped)
└── assembles: ContextPack with layer map + budget metadata
DefaultDecisionPointBuilder → L0 + L1 entries (sent to model for inference)
```
---
# major architectural outcomes
Epic 7 established:
* deterministic, rule-based context synthesis — zero model calls for compression
* five-layer context model separating live execution from compressed history
* content-type-specific compression strategies with never-drop invariants for critical entries
* token budget enforcement with correct layer eviction priority
* event-sourced context state fully rebuildable from the event log
* pack builder as a pure function — no IO, no side effects, replay-safe
---
# what Epic 7 intentionally does NOT include
not implemented:
* policy injection into context packs (step 5 of the pipeline — deferred)
* event retrieval and projection wiring for pack building (caller provides entries)
* L3/L4 storage and retrieval (infrastructure concern)
* router context isolation (separate L2 memory for the router — deferred)
* semantic summarization — no model-assisted compression of any kind
* cross-session context leaking prevention (enforced structurally by session scoping)
* observability hooks beyond event emission
those are explicitly deferred to later epics.
---
# final state
Correx now has:
> a deterministic, rule-based context synthesis engine that assembles token-budgeted, layered context packs from events and artifacts — with content-type-specific compression strategies, strict never-drop invariants for steering notes and decision history, and full event-sourced state tracking — ensuring models always receive minimal, relevant, reproducible context without any model calls in the compression path.
+46
View File
@@ -0,0 +1,46 @@
# Epic 8 — Tool System Progress
**Plan:** `docs/superpowers/plans/2026-05-11-core-tools.md`
## Done
### Task 1 — Tool shared vocabulary and lifecycle events in `core:events` ✅
- `AnyMapSerializer.kt``Map<String, Any>` ↔ JSON bridge via JsonElement
- `ToolRequest.kt`, `ToolReceipt.kt` — shared vocabulary data classes
- `ToolInvocationId` typealias added to `IdentityTypes.kt`
- 5 lifecycle event classes: `ToolInvocationRequestedEvent`, `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`, `ToolExecutionFailedEvent`, `ToolExecutionRejectedEvent`
- All 5 registered in `Serialization.kt` polymorphic block
- 8 tests passing including polymorphic round-trip
### Task 2 — `core:tools` contract types and build config ✅
- `Tool.kt` — plain `interface` (not sealed; infrastructure implements it)
- `ToolCapability.kt` — enum: FILE_READ, FILE_WRITE, NETWORK_ACCESS, SHELL_EXEC, PROCESS_SPAWN
- `ValidationResult.kt` — sealed interface: `Valid` / `Invalid(reason)`
- `build.gradle` — deps: `core:events`, `core:approvals`, `core:sessions`
- 3 tests passing
## Remaining (nothing is done, clean plate)
### Task 3 — `core:tools` state model
- `ToolInvocationStatus` enum: REQUESTED, STARTED, COMPLETED, FAILED, REJECTED
- `ToolInvocationRecord` data class (holds one invocation's full lifecycle)
- `ToolState` data class: `invocations: List<ToolInvocationRecord>`
### Task 4 — `core:tools` reducer, projector, repository
- `ToolReducer` interface + `DefaultToolReducer` (applies 5 lifecycle events to ToolState)
- `ToolProjector` (implements `Projection<ToolState>`)
- `DefaultToolRepository` (wraps `EventReplayer<ToolState>`)
### Task 5 — Mock tools in `testing:fixtures`
- `EchoTool` (T0, always Valid)
- `FailingTool` (T2, always Invalid)
- `RejectedTool` (T4, always Valid)
- `testing/fixtures/build.gradle` gains `:core:tools` dep
## Resume Instructions
1. Open this session in `/home/kami/Programs/correx`
2. Read `docs/superpowers/plans/2026-05-11-core-tools.md` for full task specs
3. Continue from **Task 3**
4. Model selection: Haiku for implementers, Sonnet for reviewers
5. No git repo in this project — skip all commit steps in the plan
+176
View File
@@ -0,0 +1,176 @@
# Epic 8 — Tool System (core:tools)
## completed deliverables
### 1. shared vocabulary in core:events
introduced tool identity, request/receipt types, and a serialization bridge for dynamic parameters.
final structures:
* `ToolInvocationId` — typealias added to `IdentityTypes.kt`
* `ToolRequest` — captures invocation identity, session, stage, tool name, and arbitrary parameters
* `ToolReceipt` — captures exit code, output summary, structured output, affected entities, duration, tier, and timestamp
* `AnyMapSerializer``Map<String, Any>` ↔ JSON bridge via `JsonElement`; required because kotlinx.serialization cannot serialize `Any` directly
key properties:
* shared vocabulary lives in `core:events` to prevent circular dependencies
* `core:tools` depends on `core:events`, not the reverse
* parameters and structured output are fully round-trip serializable
---
### 2. tool lifecycle events in core:events
introduced five event classes covering the full execution lifecycle of a tool invocation.
final events:
* `ToolInvocationRequestedEvent` — invocation submitted with request and tier
* `ToolExecutionStartedEvent` — execution begun
* `ToolExecutionCompletedEvent` — execution succeeded; carries `ToolReceipt`
* `ToolExecutionFailedEvent` — execution failed; carries reason string
* `ToolExecutionRejectedEvent` — invocation rejected before execution; carries tier and reason
all five events:
* implement `EventPayload`
* registered in `Serialization.kt` polymorphic block
* validated via 8 round-trip serialization tests
---
### 3. tool contract interface (core:tools)
defined the `Tool` interface as the stable contract that all tool implementations must satisfy.
final structures:
* `Tool` — plain interface (not sealed; infrastructure implements it)
* `ToolCapability` — enum: `FILE_READ`, `FILE_WRITE`, `NETWORK_ACCESS`, `SHELL_EXEC`, `PROCESS_SPAWN`
* `ValidationResult` — sealed interface: `Valid` / `Invalid(reason)`
interface properties:
* `name: String`
* `tier: Tier`
* `requiredCapabilities: Set<ToolCapability>`
* `validateRequest(request: ToolRequest): ValidationResult`
`core:tools` declares the contract; it never executes tools. Infrastructure modules provide concrete implementations.
---
### 4. event-sourced state model
introduced the state layer that tracks all tool invocations for a session.
final structures:
* `ToolInvocationStatus` — enum: `REQUESTED`, `STARTED`, `COMPLETED`, `FAILED`, `REJECTED`
* `ToolInvocationRecord` — holds the full lifecycle of one invocation: id, name, tier, request, status, optional receipt, requested/completed timestamps
* `ToolState` — root state: `invocations: List<ToolInvocationRecord>`
properties:
* fully `@Serializable` throughout
* state is always rebuilt from events; never persisted independently
* `receipt` and `completedAt` are nullable; populated only on terminal events
---
### 5. reducer, projector, and repository
implemented the standard CORREX pattern for event-sourced state reconstruction.
final components:
```text
ToolReducer (interface)
└── DefaultToolReducer (applies 5 lifecycle events to ToolState)
ToolProjector (implements Projection<ToolState>)
└── bridges reducer to EventReplayer infrastructure
DefaultToolRepository
└── thin wrapper over EventReplayer<ToolState>
└── getToolState(sessionId): ToolState
```
reducer behavior:
* `ToolInvocationRequestedEvent` → appends new `REQUESTED` record
* `ToolExecutionStartedEvent` → transitions matching record to `STARTED`
* `ToolExecutionCompletedEvent` → transitions to `COMPLETED`, attaches receipt and completedAt
* `ToolExecutionFailedEvent` → transitions to `FAILED`, sets completedAt
* `ToolExecutionRejectedEvent` → transitions to `REJECTED`, sets completedAt
* unrelated events → pass through unchanged
wiring (`DefaultEventReplayer(store, ToolProjector(DefaultToolReducer()))`) is left to infrastructure modules.
---
### 6. mock tools in testing:fixtures
introduced three deterministic tool implementations for use across test suites.
final tools:
* `EchoTool` — tier T0, always returns `ValidationResult.Valid`
* `FailingTool` — tier T2, always returns `ValidationResult.Invalid("simulated validation failure")`
* `RejectedTool` — tier T4, always returns `ValidationResult.Valid`
`testing/fixtures/build.gradle` gains `:core:tools` dependency.
tier assignment mirrors the approval tier model: T0 (auto-approve), T2 (requires approval), T4 (always reject).
---
# final architecture after Epic 8
```text
Tool (interface — core:tools)
ToolRequest / ToolReceipt (core:events — shared vocabulary)
5 lifecycle EventPayload subclasses (core:events)
DefaultToolReducer → ToolState (event-sourced projection)
ToolProjector → EventReplayer → DefaultToolRepository
```
---
# major architectural outcomes
Epic 8 established:
* safe tool contract: execution is infrastructure's responsibility, not core's
* full lifecycle event coverage from request to terminal state
* deterministic, replay-safe tool invocation tracking
* tier-aware tool classification aligned with the approval system
* reusable mock tools for integration and deterministic test suites
---
# what Epic 8 intentionally does NOT include
not implemented:
* concrete tool implementations (file I/O, shell, network — infrastructure concern)
* approval gating logic (Epic 5)
* tool registry or discovery mechanism
* execution engine or sandbox enforcement
* kernel orchestration of tool invocations
those are explicitly deferred to infrastructure modules and later epics.
---
# final state
Correx now has:
> a complete tool abstraction layer: a stable contract interface, a full five-event lifecycle model, event-sourced invocation state with reducer/projector/repository, and deterministic mock tools for testing — with no coupling to execution infrastructure.
+195
View File
@@ -0,0 +1,195 @@
# Epic 9 — Inference Abstraction (:core:inference)
## completed deliverables
### 1. core value types
introduced the full set of inference-domain value types as the semantic foundation of the module.
final structures:
* `ModelCapability` — sealed class (Coding, ToolCalling, Reasoning, Summarization, General)
* `CapabilityScore` — capability + score (0.01.0); used for provider selection
* `FinishReason` — sealed: Stop, Length, Timeout, Cancelled, Error(message)
* `ProviderHealth` — sealed: Healthy, Degraded(reason), Unavailable(reason)
* `CancellationReason` — sealed: UserRequested, StageTimeout, SessionCancelled, ProviderEvicted
* `InferenceTimeout` — value class wrapping Duration
* `TokenUsage` — promptTokens, completionTokens, totalTokens (derived)
* `GenerationConfig` — fully serializable; temperature, topP, maxTokens, stopSequences, seed
key properties:
* `GenerationConfig` and `TokenUsage` are `@Serializable` — replay requirement
* `ProviderHealth` and `CancellationReason` are runtime-only sealed classes — not serialized directly
* events carry string labels for cancellation context, not sealed class instances
---
### 2. inference request / response model
introduced the canonical data contract for all inference calls in the system.
final structures:
* `InferenceRequestId` — opaque value class; caller-generated, provider treats as pass-through
* `InferenceRequest` — requestId + sessionId + stageId + contextPack + generationConfig + optional timeout
* `InferenceResponse` — requestId + text + finishReason + tokensUsed + latencyMs
both are `@Serializable` for replay correctness.
`requestId` is always caller-generated — consistent with identity ownership across sessions, artifacts, and approvals modules.
---
### 3. tokenizer interface
introduced a provider-owned tokenizer abstraction, separated from the provider interface itself.
final structures:
* `Token` — value class wrapping Int
* `Tokenizer` — interface: `tokenize(text): List<Token>`, `countTokens(text): Int`
tokenizer is a property of `InferenceProvider`, not a method — makes it clear that tokenization is a static capability of the model, not a per-call operation. two providers declaring the same `ModelCapability` may not share a tokenizer.
---
### 4. provider interface and registry
introduced the core provider abstraction and its lookup contract.
final structures:
* `ProviderId` — value class
* `InferenceProvider` — interface: id, name, tokenizer, infer, healthCheck, capabilities
* `ProviderRegistry` — interface: register, resolve(capability), listAll, healthCheckAll
`resolve(capability)` returns an ordered list (score descending) — routing strategy picks from this list; registry does not select.
---
### 5. routing contracts
introduced pluggable routing as a first-class contract, not hardcoded selection logic.
final structures:
* `RoutingStrategy` — interface: `select(candidates, requiredCapabilities): InferenceProvider`
* `InferenceRouter` — interface: `route(stageId, requiredCapabilities): InferenceProvider`
* `NoEligibleProviderException` — typed failure; no nullable returns
routing is pure — no I/O, operates on a snapshot of available providers. decoupling `RoutingStrategy` from `InferenceRouter` allows selection logic (best score, round-robin, fallback chain) to be swapped without touching the router contract.
---
### 6. cancellation semantics
introduced the cancellation contract and established the coroutine cooperation rules for all provider implementations.
final structure:
* `InferenceCancellationToken` — interface: isCancelled, cancel(reason)
coroutine contract (documented as KDoc on the interface):
* `infer()` must call `ensureActive()` before the socket write, after each streamed chunk, and before parsing the final response
* blocking calls must be wrapped with `withContext(Dispatchers.IO)`
* `CancellationException` must never be swallowed
contract is intentionally documentation-only at this layer — enforcement lives in implementations (epic 11).
---
### 7. inference events and serialization module
introduced the full set of inference lifecycle events and registered them in the polymorphic serialization system.
events:
* `InferenceStartedEvent` — requestId, sessionId, stageId, providerId
* `InferenceCompletedEvent` — requestId, sessionId, stageId, providerId, tokensUsed, latencyMs
* `InferenceFailedEvent` — requestId, sessionId, stageId, providerId, reason (string)
* `InferenceTimeoutEvent` — requestId, sessionId, stageId, providerId, timeoutMs
* `ModelLoadedEvent` — providerId, sessionId
* `ModelUnloadedEvent` — providerId, sessionId, cancellationReason (string, optional)
all events carry `requestId` for causation tracing back to the originating call.
`inferenceModule: SerializersModule` registers all payloads — follows the same pattern as `eventModule`, `artifactModule`, `contextModule`.
---
### 8. mock provider and contract test infrastructure
introduced a deterministic fake provider for contract validation and test isolation.
final structures:
* `MockTokenizer` — character-based approximation (1 token ≈ 4 chars); deterministic; not model-accurate
* `MockInferenceProvider` — configurable: fixed response, artificial delay, forced failure; exposes `inferCallCount` for assertion
* `InferenceProviderContractTest` — abstract contract test class; same shape as `EventStoreContractTest`
contract tests cover:
* response carries matching requestId
* text is non-blank
* token usage is tracked and positive
* latency is non-negative
* healthCheck returns non-null
* capabilities non-empty with scores in 0.01.0
* coroutine cancellation is respected
* tokenizer count is consistent with tokenize
---
# final architecture after Epic 9
```text
InferenceRouter (capability-driven routing)
RoutingStrategy (pluggable selection logic)
ProviderRegistry (capability → provider resolution)
InferenceProvider (stateless compute contract)
├── tokenizer: Tokenizer
├── infer(InferenceRequest): InferenceResponse
└── capabilities(): Set<CapabilityScore>
InferenceEvents → EventStore (via core:events)
```
---
# major architectural outcomes
Epic 9 established:
* stable inference provider abstraction — models are interchangeable, infrastructure-independent
* capability-based routing with pluggable selection strategy
* provider-owned tokenization — no shared tokenizer assumption across model families
* caller-generated request identity — consistent with system-wide identity ownership model
* cooperative cancellation contract — enforced at implementation layer, documented at contract layer
* full inference lifecycle event coverage with causation tracing via requestId
* replay-safe serialization for all request/response/config types
* mock provider and abstract contract tests for deterministic validation in all future epics
---
# what Epic 9 intentionally does NOT include
not implemented:
* inference state projection / reducer / repository — deferred to Epic 10 where the orchestrator provides a concrete consumer
* actual provider implementations (llama.cpp, ollama) — infrastructure concern, Epic 11
* streaming response handling — deferred; contract is text-final only for now
* GPU residency scheduling — infrastructure concern, Epic 11
* router context isolation (separate L2 memory per provider) — deferred to Epic 13
---
# final state
`:core:inference` provides:
> a fully specified, infrastructure-independent inference abstraction layer with capability-based routing, cooperative cancellation semantics, replay-safe request/response contracts, and a deterministic mock provider — ready to be composed by the orchestration kernel in Epic 10.
+331
View File
@@ -0,0 +1,331 @@
below is a cleaned, dependency-aware epic structure aligned with your invariants ADR. order matters; this is a build sequence, not just grouping.
---
# EPIC 0: Core contracts & identity system (foundation layer)
goal: define all shared primitives used across the system
deliverables:
* `Event`
* `Artifact`
* `SessionId`, `EventId`, `CorrelationId`, `CausationId`
* `StageId`, `ToolId`
* base envelope types (`EventEnvelope`, `Command`, `Result`)
* versioning model for all serialized data
* error model (typed, replay-safe)
dependencies: none
why first: everything else depends on these invariants being stable
---
# EPIC 1: Event system (:core:events)
goal: deterministic append-only event backbone
deliverables:
* event store interface
* in-memory store (test)
* sqlite store (infra later)
* ordering guarantees per session
* causality enforcement
* serialization layer (kotlinx.serialization)
* idempotency rules
dependencies: epic 0
---
# EPIC 2: Session lifecycle (:core:sessions)
goal: deterministic session FSM + projection
deliverables:
* session states + transitions
* session projection model
* recovery from event stream
* pause/resume/cancel semantics
* session event reducers
dependencies:
* epic 0
* epic 1
---
# EPIC 3: Transition engine (:core:transitions)
goal: workflow graph execution engine
deliverables:
* transition DSL
* graph model
* cycle/unreachable detection
* deterministic evaluation rules
* stage execution contract
dependencies:
* epic 0
* epic 1
---
# EPIC 4: Validation pipeline (:core:validation)
goal: deterministic multi-layer validation system
deliverables:
* routing validation
* schema validation
* semantic validation hooks
* pipeline executor
* approval trigger integration point (no execution coupling)
dependencies:
* epic 0
* epic 1
* epic 3
---
# EPIC 5: Approval system (:core:approvals)
goal: tiered, replay-safe authorization layer
deliverables:
* tier model (T0T4)
* session/stage/project grants
* approval lifecycle events
* timeout semantics
* diff/preview contract interface
* steering injection model
dependencies:
* epic 0
* epic 1
* epic 4
---
# EPIC 6: Artifact system (:core:artifacts)
goal: immutable outputs with lineage tracking
deliverables:
* artifact schema
* lineage graph model
* structured summary fields (non-authoritative)
* validation results attachment
* serialization rules
dependencies:
* epic 0
* epic 1
---
# EPIC 7: Context engine (:core:context)
goal: deterministic context synthesis layer
deliverables:
* context layers (L0L3)
* compression pipeline (rule-based only)
* token budgeting
* decision point builder (from events + artifacts)
* context pack generator
dependencies:
* epic 0
* epic 1
* epic 6
---
# EPIC 8: Tool system (:core:tools)
goal: safe tool execution abstraction
deliverables:
* tool interface contract
* execution lifecycle model
* receipt schema (mandatory structured output)
* sandbox contract definition
* tier assignment per tool
* mock tools for testing
dependencies:
* epic 0
* epic 1
* epic 5
---
# EPIC 9: Inference abstraction (:core:inference)
goal: pluggable model execution layer
deliverables:
* provider interface
* request/response model
* capability registry
* routing logic (model selection)
* cancellation/timeout semantics
* mock provider
dependencies:
* epic 0
* epic 1
* epic 7
---
# EPIC 10: Kernel orchestration (:core:kernel)
goal: deterministic execution engine (system brain)
deliverables:
* SessionOrchestrator
* WorkflowCoordinator
* ExecutionScheduler
* main event loop
* integration of:
* validation
* approvals
* inference
* tools
* transitions
* replay driver
dependencies:
* everything above (epics 09)
---
# EPIC 11: Infrastructure layer
goal: real-world adapters for persistence and execution
deliverables:
* sqlite event store
* snapshot store
* inference providers (llama.cpp, ollama, etc.)
* tool implementations (shell/git/fs)
* sandboxing layer
* telemetry exporters
dependencies:
* epic 1
* epic 8
* epic 9
---
# EPIC 12: Technical debt and refactoring
goal: fix the known problems and bugs, close the discovered gaps
deliverables:
* no known technical debt is present
* 95% of classes are refactored with concrete rules applied
* opened bugs are closed and verified with appropriate tests
dependencies:
* all previous epics
---
# EPIC 13: Interfaces (CLI + API)
goal: user interaction layer
deliverables:
* CLI (Clikt)
* Ktor API server
* websocket event stream
* session control endpoints
* approval interaction endpoints
dependencies:
* epic 10
* epic 11
---
# EPIC 14: Router (:core:router)
goal: user-facing orchestration façade
deliverables:
* conversational memory layer (L2/L3 bridge)
* summarization rules (non-authoritative)
* steering interpretation
* UX-level session framing
dependencies:
* epic 7
* epic 10
---
# EPIC 15: Observability & replay tooling
goal: debugging + deterministic inspection system
deliverables:
* structured logging with correlation
* event stream inspector
* full session replay tool
* deterministic test harness
* fuzz testing for transitions/approvals
* trace visualization (optional later)
dependencies:
* epic 1
* epic 10
* epic 11
---
## ordering summary (critical path)
```text id="epic-order"
0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15
```
---
if you want next step, the useful move is not more design, but:
* defining **event schemas for EPIC 1 + EPIC 2 together**
* because thats where most systems like this silently break consistency later
+21
View File
@@ -0,0 +1,21 @@
{
"active": 13,
"completed": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
],
"current_task": "",
"done_tasks": [],
"next_tasks": [],
"deferred": []
}