docs: add module documentation in AsciiDoc with PlantUML diagrams
Generated by per-group subagents covering all 52 modules across: core (18), infrastructure (10), apps (5), interfaces (3), plugins (7), testing (9) Documentation format: - AsciiDoc (.adoc) files in docs/modules/<group>/ - PlantUML (.puml) files in docs/diagrams/ - .adoc files reference diagrams via include:: directives Each doc covers: purpose, responsibilities, non-responsibilities, key types, event flow, integration points, invariants, PlantUML diagram, known issues, and open questions.
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
@startuml
|
||||||
|
actor User
|
||||||
|
participant CLI
|
||||||
|
participant "Server\n(REST)" as REST
|
||||||
|
participant "Server\n(WS)" as WS
|
||||||
|
|
||||||
|
User -> CLI: correx run --workflow wf.toml
|
||||||
|
CLI -> REST: POST /sessions {workflowId}
|
||||||
|
REST --> CLI: sessionId
|
||||||
|
CLI -> WS: ws /sessions/{id}/stream
|
||||||
|
loop until SessionCompleted/Failed
|
||||||
|
WS --> CLI: StageStarted/Completed
|
||||||
|
WS --> CLI: ToolStarted/Completed/Failed
|
||||||
|
WS --> CLI: ApprovalRequired
|
||||||
|
User -> CLI: approve/reject/steer
|
||||||
|
CLI -> WS: ApprovalResponse
|
||||||
|
end
|
||||||
|
WS --> CLI: SessionCompleted/Failed
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@startuml
|
||||||
|
start
|
||||||
|
:print "correx :: apps/desktop";
|
||||||
|
stop
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
@startuml
|
||||||
|
package "Server Module" {
|
||||||
|
[ServerModule] as SM
|
||||||
|
[GlobalStreamHandler] as GSH
|
||||||
|
[SessionStreamHandler] as SSH
|
||||||
|
[ApprovalCoordinator] as AC
|
||||||
|
[DomainEventMapper] as DEM
|
||||||
|
[LoggingEventStore] as LES
|
||||||
|
[SessionEventBridge] as SEB
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Core Domain" {
|
||||||
|
[DefaultSessionOrchestrator] as DSO
|
||||||
|
[EventStore] as ES
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Infrastructure" {
|
||||||
|
[DefaultProviderRegistry] as DPR
|
||||||
|
[ToolExecutor] as TE
|
||||||
|
[FileSystemWorkflowRegistry] as FSWR
|
||||||
|
}
|
||||||
|
|
||||||
|
SM --> LES : decorates
|
||||||
|
SM --> DSO : orchestrator
|
||||||
|
SM --> FSWR : workflowRegistry
|
||||||
|
SM --> AC : creates
|
||||||
|
SM --> ES : subscribeAll
|
||||||
|
|
||||||
|
GSH --> SM : reads registries
|
||||||
|
GSH --> DEM : map events
|
||||||
|
GSH --> SEB : replaySnapshot
|
||||||
|
|
||||||
|
SSH --> AC : registerClient
|
||||||
|
SSH --> DEM : map events
|
||||||
|
SSH --> DSO : cancel
|
||||||
|
|
||||||
|
AC --> DSO : submitApprovalDecision
|
||||||
|
AC --> SM.moduleScope : timeout jobs
|
||||||
|
|
||||||
|
LES --> ES : delegates
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@startuml
|
||||||
|
[KeyResolver] -> [RootReducer] : Action
|
||||||
|
[WsClient.messages] -> [RootReducer] : ServerEventReceived
|
||||||
|
[WsClient.connection] -> [RootReducer] : Connected/Disconnected/RetryScheduled
|
||||||
|
|
||||||
|
[RootReducer] --> [SnapshotPhaseReducer]
|
||||||
|
[RootReducer] --> [InputReducer]
|
||||||
|
[RootReducer] --> [SessionsReducer]
|
||||||
|
[RootReducer] --> [ConnectionReducer]
|
||||||
|
[RootReducer] --> [ProviderReducer]
|
||||||
|
|
||||||
|
[RootReducer] -> [EffectDispatcher] : List<Effect>
|
||||||
|
[EffectDispatcher] -> [WsClient] : SendWs
|
||||||
|
[EffectDispatcher] -> [System] : Quit
|
||||||
|
|
||||||
|
clock over [WsClient] : exponential backoff reconnect
|
||||||
|
|
||||||
|
[RootReducer] --> [Renderer] : TuiState
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@startuml
|
||||||
|
start
|
||||||
|
:print "correx :: apps/worker";
|
||||||
|
stop
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "core-agents: no source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
@startuml
|
||||||
|
interface ApprovalReducer
|
||||||
|
interface ApprovalEngine
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultApprovalReducer
|
||||||
|
class DefaultApprovalEngine
|
||||||
|
class NoOpApprovalEngine
|
||||||
|
class ApprovalProjector
|
||||||
|
class DefaultApprovalRepository
|
||||||
|
class ApprovalState
|
||||||
|
class DomainApprovalRequest
|
||||||
|
class ApprovalDecision
|
||||||
|
class ApprovalGrant
|
||||||
|
class ApprovalContext
|
||||||
|
|
||||||
|
ApprovalReducer <|.. DefaultApprovalReducer
|
||||||
|
ApprovalEngine <|.. DefaultApprovalEngine
|
||||||
|
ApprovalEngine <|.. NoOpApprovalEngine
|
||||||
|
Projection <|.. ApprovalProjector
|
||||||
|
ApprovalProjector --> ApprovalReducer
|
||||||
|
DefaultApprovalRepository --> ApprovalProjector
|
||||||
|
DefaultApprovalRepository ..> ApprovalState
|
||||||
|
DefaultApprovalState --> "requests" DomainApprovalRequest
|
||||||
|
DefaultApprovalState --> "decisions" ApprovalDecision
|
||||||
|
DefaultApprovalState --> "grants" ApprovalGrant
|
||||||
|
ApprovalDecision --> ApprovalContext
|
||||||
|
DefaultApprovalEngine ..> ApprovalGrant
|
||||||
|
DefaultApprovalEngine ..> ApprovalDecision
|
||||||
|
DefaultApprovalEngine ..> ApprovalContext
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[ApprovalRequestedEvent]
|
||||||
|
[ApprovalDecisionResolvedEvent]
|
||||||
|
[ApprovalGrantCreatedEvent]
|
||||||
|
[ApprovalGrantExpiredEvent]
|
||||||
|
}
|
||||||
|
package "core:sessions" <<External>> {
|
||||||
|
[Projection]
|
||||||
|
[EventReplayer]
|
||||||
|
}
|
||||||
|
DefaultApprovalReducer ..> "core:events"
|
||||||
|
DefaultApprovalRepository ..> "core:events"
|
||||||
|
DefaultApprovalRepository ..> "core:sessions"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
@startuml
|
||||||
|
interface ArtifactStore
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[ArtifactId]
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtifactStore ..> "core:events"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
@startuml
|
||||||
|
interface Artifact
|
||||||
|
interface ArtifactReducer
|
||||||
|
interface ArtifactKind
|
||||||
|
interface ArtifactKindRegistry
|
||||||
|
interface ArtifactRepository
|
||||||
|
interface MaterializingArtifactWriter
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultArtifactReducer
|
||||||
|
class DefaultArtifactKindRegistry
|
||||||
|
class ArtifactProjector
|
||||||
|
|
||||||
|
class ArtifactState
|
||||||
|
class ArtifactLineage
|
||||||
|
class ArtifactRelationship
|
||||||
|
class FileWrittenKind
|
||||||
|
class ProcessResultKind
|
||||||
|
class FileWrittenPayload
|
||||||
|
class FileWrittenArtifact
|
||||||
|
class ProcessResultArtifact
|
||||||
|
class TypedArtifactSlot
|
||||||
|
|
||||||
|
ArtifactReducer <|.. DefaultArtifactReducer
|
||||||
|
ArtifactKindRegistry <|.. DefaultArtifactKindRegistry
|
||||||
|
ArtifactKind <|.. FileWrittenKind
|
||||||
|
ArtifactKind <|.. ProcessResultKind
|
||||||
|
Projection <|.. ArtifactProjector
|
||||||
|
ArtifactProjector --> ArtifactReducer
|
||||||
|
|
||||||
|
DefaultArtifactKindRegistry --> ArtifactKind
|
||||||
|
DefaultArtifactReducer ..> ArtifactState
|
||||||
|
ArtifactState --> ArtifactLineage
|
||||||
|
ArtifactLineage --> "relationships" ArtifactRelationship
|
||||||
|
|
||||||
|
FileWrittenKind ..> FileWrittenPayload
|
||||||
|
FileWrittenKind ..> FileWrittenArtifact
|
||||||
|
ProcessResultKind ..> ProcessResultArtifact
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[ArtifactCreatedEvent]
|
||||||
|
[ArtifactValidatingEvent]
|
||||||
|
[ArtifactValidatedEvent]
|
||||||
|
[ArtifactRejectedEvent]
|
||||||
|
[ArtifactSupersededEvent]
|
||||||
|
[ArtifactArchivedEvent]
|
||||||
|
[ArtifactRelationshipAddedEvent]
|
||||||
|
}
|
||||||
|
package "core:artifacts-store" <<External>> {
|
||||||
|
[ArtifactStore]
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultArtifactReducer ..> "core:events"
|
||||||
|
ArtifactProjector ..> "core:events"
|
||||||
|
MaterializingArtifactWriter ..> "core:artifacts-store"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "core-config" {
|
||||||
|
object ConfigLoader
|
||||||
|
class CorrexConfig
|
||||||
|
class ServerConfig
|
||||||
|
class TuiConfig
|
||||||
|
class CliConfig
|
||||||
|
class ApprovalConfig
|
||||||
|
class ToolsConfig
|
||||||
|
}
|
||||||
|
ConfigLoader --> CorrexConfig : loads
|
||||||
|
CorrexConfig *--> ServerConfig
|
||||||
|
CorrexConfig *--> TuiConfig
|
||||||
|
CorrexConfig *--> CliConfig
|
||||||
|
CorrexConfig *--> ApprovalConfig
|
||||||
|
CorrexConfig *--> ToolsConfig
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
@startuml
|
||||||
|
interface ContextPackBuilder
|
||||||
|
interface DecisionPointBuilder
|
||||||
|
interface ContextCompressor
|
||||||
|
interface ContextReducer
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultContextPackBuilder
|
||||||
|
class DefaultDecisionPointBuilder
|
||||||
|
class DefaultContextCompressor
|
||||||
|
class DefaultContextReducer
|
||||||
|
class ContextProjector
|
||||||
|
class DefaultContextRepository
|
||||||
|
|
||||||
|
class ContextPack
|
||||||
|
class ContextEntry
|
||||||
|
class ContextLayer
|
||||||
|
class TokenBudget
|
||||||
|
class CompressionMetadata
|
||||||
|
class CompressionStrategy
|
||||||
|
class SteeringNote
|
||||||
|
class ContextState
|
||||||
|
|
||||||
|
ContextPackBuilder <|.. DefaultContextPackBuilder
|
||||||
|
DecisionPointBuilder <|.. DefaultDecisionPointBuilder
|
||||||
|
ContextCompressor <|.. DefaultContextCompressor
|
||||||
|
ContextReducer <|.. DefaultContextReducer
|
||||||
|
Projection <|.. ContextProjector
|
||||||
|
ContextProjector --> ContextReducer
|
||||||
|
DefaultContextRepository --> ContextProjector
|
||||||
|
|
||||||
|
DefaultContextPackBuilder --> ContextCompressor
|
||||||
|
DefaultContextPackBuilder ..> ContextPack
|
||||||
|
DefaultContextPackBuilder --> TokenBudget
|
||||||
|
ContextPack --> "layers" ContextLayer
|
||||||
|
ContextPack --> "entries" ContextEntry
|
||||||
|
ContextPack --> CompressionMetadata
|
||||||
|
DefaultContextCompressor --> CompressionStrategy
|
||||||
|
DefaultContextCompressor --> TokenBudget
|
||||||
|
DefaultDecisionPointBuilder ..> ContextPack
|
||||||
|
DefaultDecisionPointBuilder --> ContextEntry
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[ContextBuildingStartedEvent]
|
||||||
|
[ContextPackBuiltEvent]
|
||||||
|
[ContextBuildingFailedEvent]
|
||||||
|
[ContextBuildingInterruptedEvent]
|
||||||
|
}
|
||||||
|
package "core:sessions" <<External>> {
|
||||||
|
[Projection]
|
||||||
|
[EventReplayer]
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultContextReducer ..> "core:events"
|
||||||
|
DefaultContextRepository ..> "core:events"
|
||||||
|
DefaultContextRepository ..> "core:sessions"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
@startuml
|
||||||
|
package "core:events" {
|
||||||
|
interface EventPayload
|
||||||
|
|
||||||
|
abstract class NewEvent {
|
||||||
|
+ metadata: EventMetadata
|
||||||
|
+ payload: EventPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class StoredEvent {
|
||||||
|
+ metadata: EventMetadata
|
||||||
|
+ sequence: Long
|
||||||
|
+ sessionSequence: Long
|
||||||
|
+ payload: EventPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventMetadata {
|
||||||
|
+ eventId: EventId
|
||||||
|
+ sessionId: SessionId
|
||||||
|
+ timestamp: Instant
|
||||||
|
+ schemaVersion: Int
|
||||||
|
+ causationId: CausationId?
|
||||||
|
+ correlationId: CorrelationId?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventStore {
|
||||||
|
+ append(NewEvent): StoredEvent
|
||||||
|
+ read(SessionId): List<StoredEvent>
|
||||||
|
+ subscribe(SessionId): Flow<StoredEvent>
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventDispatcher {
|
||||||
|
+ emit(EventPayload, SessionId, ...)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventSerializer {
|
||||||
|
+ serialize(EventPayload): String
|
||||||
|
+ deserialize(String): EventPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonEventSerializer
|
||||||
|
|
||||||
|
interface Projection<S> {
|
||||||
|
+ initial(): S
|
||||||
|
+ apply(S, StoredEvent): S
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateBuilder<S> {
|
||||||
|
+ build(List<StoredEvent>): S
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultStateBuilder<S>
|
||||||
|
|
||||||
|
interface EventReplayer<S> {
|
||||||
|
+ rebuild(SessionId): S
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultEventReplayer<S>
|
||||||
|
|
||||||
|
EventPayload <|.. SessionStartedEvent
|
||||||
|
EventPayload <|.. SessionPausedEvent
|
||||||
|
EventPayload <|.. WorkflowStartedEvent
|
||||||
|
EventPayload <|.. WorkflowCompletedEvent
|
||||||
|
EventPayload <|.. StageStartedEvent
|
||||||
|
EventPayload <|.. StageCompletedEvent
|
||||||
|
EventPayload <|.. TransitionExecutedEvent
|
||||||
|
EventPayload <|.. InferenceStartedEvent
|
||||||
|
EventPayload <|.. InferenceCompletedEvent
|
||||||
|
EventPayload <|.. ToolInvocationRequestedEvent
|
||||||
|
EventPayload <|.. ApprovalRequestedEvent
|
||||||
|
EventPayload <|.. ApprovalDecisionResolvedEvent
|
||||||
|
EventPayload <|.. ArtifactCreatedEvent
|
||||||
|
EventPayload <|.. ArtifactValidatedEvent
|
||||||
|
EventPayload <|.. RiskAssessedEvent
|
||||||
|
EventPayload <|.. ContextPackBuiltEvent
|
||||||
|
EventPayload <|.. OrchestrationPausedEvent
|
||||||
|
EventPayload <|.. OrchestrationResumedEvent
|
||||||
|
EventPayload <|.. RetryAttemptedEvent
|
||||||
|
|
||||||
|
NewEvent *-- EventMetadata
|
||||||
|
NewEvent *-- EventPayload
|
||||||
|
StoredEvent *-- EventMetadata
|
||||||
|
StoredEvent *-- EventPayload
|
||||||
|
EventDispatcher --> EventStore
|
||||||
|
EventDispatcher ..> NewEvent : creates
|
||||||
|
|
||||||
|
DefaultStateBuilder --> Projection
|
||||||
|
DefaultEventReplayer --> EventStore
|
||||||
|
DefaultEventReplayer --> DefaultStateBuilder
|
||||||
|
|
||||||
|
JsonEventSerializer ..|> EventSerializer
|
||||||
|
JsonEventSerializer --> eventJson
|
||||||
|
|
||||||
|
note right of EventPayload : 35+ concrete subtypes\nregistered in polymorphic block
|
||||||
|
}
|
||||||
|
|
||||||
|
package "shared types" {
|
||||||
|
value class TypeId
|
||||||
|
enum Tier
|
||||||
|
enum RiskLevel
|
||||||
|
enum RiskAction
|
||||||
|
sealed interface ApprovalStatus
|
||||||
|
enum ApprovalOutcome
|
||||||
|
sealed interface GrantScope
|
||||||
|
data class RetryPolicy
|
||||||
|
data class TokenUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId <|.. SessionId
|
||||||
|
TypeId <|.. StageId
|
||||||
|
TypeId <|.. EventId
|
||||||
|
TypeId <|.. ArtifactId
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
@startuml
|
||||||
|
interface InferenceProvider
|
||||||
|
interface ProviderRegistry
|
||||||
|
interface InferenceRouter
|
||||||
|
interface RoutingStrategy
|
||||||
|
interface InferenceCancellationToken
|
||||||
|
interface Tokenizer
|
||||||
|
interface InferenceReducer
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultInferenceRouter
|
||||||
|
class DefaultInferenceReducer
|
||||||
|
class InferenceProjector
|
||||||
|
class InferenceRepository
|
||||||
|
|
||||||
|
class InferenceRequest
|
||||||
|
class InferenceResponse
|
||||||
|
class InferenceState
|
||||||
|
class InferenceRecord
|
||||||
|
class GenerationConfig
|
||||||
|
class ToolCallRequest
|
||||||
|
class ToolDefinition
|
||||||
|
class ModelCapability
|
||||||
|
class CapabilityScore
|
||||||
|
class ProviderHealth
|
||||||
|
class FinishReason
|
||||||
|
class ResponseFormat
|
||||||
|
|
||||||
|
enum InferenceStatus
|
||||||
|
|
||||||
|
InferenceRouter <|.. DefaultInferenceRouter
|
||||||
|
RoutingStrategy <|-- (DefaultInferenceRouter uses)
|
||||||
|
InferenceProvider <|-- (DefaultInferenceRouter uses)
|
||||||
|
ProviderRegistry <|-- (DefaultInferenceRouter uses)
|
||||||
|
InferenceReducer <|.. DefaultInferenceReducer
|
||||||
|
Projection <|.. InferenceProjector
|
||||||
|
InferenceProjector --> InferenceReducer
|
||||||
|
InferenceRepository --> InferenceProjector
|
||||||
|
|
||||||
|
DefaultInferenceRouter --> RoutingStrategy
|
||||||
|
DefaultInferenceRouter --> ProviderRegistry
|
||||||
|
InferenceRequest --> GenerationConfig
|
||||||
|
InferenceRequest --> "contextPack" ContextPack
|
||||||
|
InferenceRequest --> "tools" ToolDefinition
|
||||||
|
InferenceResponse --> "toolCalls" ToolCallRequest
|
||||||
|
InferenceResponse --> FinishReason
|
||||||
|
InferenceProvider --> Tokenizer
|
||||||
|
InferenceRecord --> InferenceStatus
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[InferenceStartedEvent]
|
||||||
|
[InferenceCompletedEvent]
|
||||||
|
[InferenceFailedEvent]
|
||||||
|
[InferenceTimeoutEvent]
|
||||||
|
}
|
||||||
|
package "core:context" <<External>> {
|
||||||
|
[ContextPack]
|
||||||
|
}
|
||||||
|
package "core:artifacts" <<External>> {
|
||||||
|
[JsonSchema]
|
||||||
|
}
|
||||||
|
package "core:sessions" <<External>> {
|
||||||
|
[Projection]
|
||||||
|
[EventReplayer]
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultInferenceReducer ..> "core:events"
|
||||||
|
InferenceRepository ..> "core:events"
|
||||||
|
InferenceRepository ..> "core:sessions"
|
||||||
|
InferenceRequest ..> "core:context"
|
||||||
|
ResponseFormat.Json ..> "core:artifacts"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
@startuml
|
||||||
|
package "core:kernel" {
|
||||||
|
abstract class SessionOrchestrator {
|
||||||
|
# emit()
|
||||||
|
# executeStage()
|
||||||
|
# runInference()
|
||||||
|
# resolveTransition()
|
||||||
|
+ abstract run()
|
||||||
|
+ abstract cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultSessionOrchestrator {
|
||||||
|
+ run()
|
||||||
|
+ cancel()
|
||||||
|
+ submitApprovalDecision()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplayOrchestrator {
|
||||||
|
+ run()
|
||||||
|
+ cancel()
|
||||||
|
# runInference()
|
||||||
|
# mapValidationOutcome()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApprovalGateway {
|
||||||
|
+ submitApprovalDecision()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RetryCoordinator
|
||||||
|
class DefaultRetryCoordinator
|
||||||
|
|
||||||
|
data class OrchestratorRepositories
|
||||||
|
data class OrchestratorEngines
|
||||||
|
|
||||||
|
data class OrchestrationConfig
|
||||||
|
sealed interface WorkflowResult
|
||||||
|
sealed interface ReplayStrategy
|
||||||
|
sealed interface StageOutcome
|
||||||
|
interface OrchestrationReducer
|
||||||
|
class DefaultOrchestrationReducer
|
||||||
|
class OrchestrationProjector
|
||||||
|
class OrchestrationRepository
|
||||||
|
|
||||||
|
class ReplayInferenceProvider
|
||||||
|
class ReplayArtifactMissingException
|
||||||
|
|
||||||
|
DefaultSessionOrchestrator --|> SessionOrchestrator
|
||||||
|
DefaultSessionOrchestrator ..|> ApprovalGateway
|
||||||
|
ReplayOrchestrator --|> SessionOrchestrator
|
||||||
|
|
||||||
|
SessionOrchestrator *-- OrchestratorRepositories
|
||||||
|
SessionOrchestrator *-- OrchestratorEngines
|
||||||
|
SessionOrchestrator --> RetryCoordinator
|
||||||
|
|
||||||
|
DefaultRetryCoordinator ..|> RetryCoordinator
|
||||||
|
DefaultRetryCoordinator --> EventStore
|
||||||
|
|
||||||
|
ReplayOrchestrator --> ReplayInferenceProvider
|
||||||
|
|
||||||
|
OrchestrationProjector --> OrchestrationReducer
|
||||||
|
OrchestrationRepository --> EventReplayer
|
||||||
|
}
|
||||||
|
|
||||||
|
package "external modules" as EXT {
|
||||||
|
package ":core:events" as EV {
|
||||||
|
interface EventStore
|
||||||
|
interface EventReplayer
|
||||||
|
interface Projection
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:sessions" as SES {
|
||||||
|
class DefaultSessionRepository
|
||||||
|
data class Session
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:transitions" as TR {
|
||||||
|
interface TransitionResolver
|
||||||
|
data class WorkflowGraph
|
||||||
|
data class StageExecutionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:inference" as INF {
|
||||||
|
interface InferenceRouter
|
||||||
|
interface InferenceRepository
|
||||||
|
data class InferenceResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:context" as CTX {
|
||||||
|
interface ContextPackBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:validation" as VAL {
|
||||||
|
interface ValidationPipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:approvals" as APR {
|
||||||
|
class DefaultApprovalRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:risk" as RSK {
|
||||||
|
interface RiskAssessor
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:tools" as TOL {
|
||||||
|
interface ToolExecutor
|
||||||
|
interface ToolRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:artifacts-store" as AST {
|
||||||
|
interface ArtifactStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionOrchestrator --> EV
|
||||||
|
SessionOrchestrator --> SES
|
||||||
|
SessionOrchestrator --> TR
|
||||||
|
SessionOrchestrator --> INF
|
||||||
|
SessionOrchestrator --> CTX
|
||||||
|
SessionOrchestrator --> VAL
|
||||||
|
SessionOrchestrator --> APR
|
||||||
|
SessionOrchestrator --> RSK
|
||||||
|
SessionOrchestrator --> TOL
|
||||||
|
SessionOrchestrator --> AST
|
||||||
|
|
||||||
|
ReplayOrchestrator --> EXT
|
||||||
|
|
||||||
|
note top of DefaultSessionOrchestrator : Live execution with\nreal inference, validation,\nrisk assessment, approvals
|
||||||
|
note top of ReplayOrchestrator : Deterministic replay from\nevent log only
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "core-observability" {
|
||||||
|
object Module
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "core-policies" {
|
||||||
|
object Module
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "core-risk" {
|
||||||
|
interface RiskAssessor
|
||||||
|
class DefaultRiskAssessor
|
||||||
|
class NoOpRiskAssessor
|
||||||
|
class RiskContext
|
||||||
|
class TierMapping {
|
||||||
|
+toApprovalTier()
|
||||||
|
+toRiskAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RiskAssessor <|.. DefaultRiskAssessor
|
||||||
|
RiskAssessor <|.. NoOpRiskAssessor
|
||||||
|
DefaultRiskAssessor --> RiskContext : assess()
|
||||||
|
NoOpRiskAssessor --> RiskContext : assess()
|
||||||
|
TierMapping --> RiskAssessor : used by
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
@startuml
|
||||||
|
interface RouterFacade
|
||||||
|
interface RouterRepository
|
||||||
|
interface RouterContextBuilder
|
||||||
|
interface RouterReducer
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultRouterFacade
|
||||||
|
class DefaultRouterRepository
|
||||||
|
class DefaultRouterContextBuilder
|
||||||
|
class DefaultRouterReducer
|
||||||
|
class RouterProjector
|
||||||
|
|
||||||
|
class RouterState
|
||||||
|
class RouterConfig
|
||||||
|
class RouterResponse
|
||||||
|
class RouterL2Entry
|
||||||
|
class RouterTurn
|
||||||
|
enum WorkflowStatus
|
||||||
|
enum StageOutcomeKind
|
||||||
|
enum TurnRole
|
||||||
|
enum ChatMode
|
||||||
|
|
||||||
|
RouterFacade <|.. DefaultRouterFacade
|
||||||
|
RouterRepository <|.. DefaultRouterRepository
|
||||||
|
RouterContextBuilder <|.. DefaultRouterContextBuilder
|
||||||
|
RouterReducer <|.. DefaultRouterReducer
|
||||||
|
Projection <|.. RouterProjector
|
||||||
|
RouterProjector --> RouterReducer
|
||||||
|
DefaultRouterRepository --> RouterProjector
|
||||||
|
DefaultRouterRepository ..> RouterState
|
||||||
|
|
||||||
|
DefaultRouterFacade --> RouterRepository
|
||||||
|
DefaultRouterFacade --> RouterContextBuilder
|
||||||
|
DefaultRouterFacade --> RouterConfig
|
||||||
|
DefaultRouterFacade ..> RouterResponse
|
||||||
|
DefaultRouterContextBuilder --> RouterConfig
|
||||||
|
DefaultRouterContextBuilder --> RouterState
|
||||||
|
DefaultRouterContextBuilder ..> "ContextPack"
|
||||||
|
|
||||||
|
RouterState --> "l2Memory" RouterL2Entry
|
||||||
|
RouterState --> "conversationHistory" RouterTurn
|
||||||
|
RouterState --> WorkflowStatus
|
||||||
|
RouterL2Entry --> StageOutcomeKind
|
||||||
|
RouterTurn --> TurnRole
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[EventStore]
|
||||||
|
[SteeringNoteAddedEvent]
|
||||||
|
[WorkflowStartedEvent]
|
||||||
|
[WorkflowCompletedEvent]
|
||||||
|
[WorkflowFailedEvent]
|
||||||
|
[OrchestrationPausedEvent]
|
||||||
|
[OrchestrationResumedEvent]
|
||||||
|
[StageCompletedEvent]
|
||||||
|
[StageFailedEvent]
|
||||||
|
}
|
||||||
|
package "core:inference" <<External>> {
|
||||||
|
[InferenceRouter]
|
||||||
|
[InferenceProvider]
|
||||||
|
[InferenceRequest]
|
||||||
|
}
|
||||||
|
package "core:context" <<External>> {
|
||||||
|
[ContextPack]
|
||||||
|
}
|
||||||
|
package "core:sessions" <<External>> {
|
||||||
|
[Projection]
|
||||||
|
[EventReplayer]
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultRouterFacade ..> "core:events"
|
||||||
|
DefaultRouterFacade ..> "core:inference"
|
||||||
|
DefaultRouterContextBuilder ..> "core:context"
|
||||||
|
DefaultRouterRepository ..> "core:events"
|
||||||
|
DefaultRouterRepository ..> "core:sessions"
|
||||||
|
DefaultRouterReducer ..> "core:events"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
@startuml
|
||||||
|
package "core:sessions" {
|
||||||
|
enum SessionStatus {
|
||||||
|
CREATED
|
||||||
|
ACTIVE
|
||||||
|
PAUSED
|
||||||
|
COMPLETED
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SessionState {
|
||||||
|
+ status: SessionStatus
|
||||||
|
+ createdAt: Instant?
|
||||||
|
+ updatedAt: Instant?
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Session {
|
||||||
|
+ sessionId: SessionId
|
||||||
|
+ state: SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SessionReducer {
|
||||||
|
+ reduce(SessionState, StoredEvent): SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultSessionReducer
|
||||||
|
|
||||||
|
class SessionProjector
|
||||||
|
|
||||||
|
class DefaultSessionRepository {
|
||||||
|
+ getSession(SessionId): Session
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface TransitionResult {
|
||||||
|
data class Applied
|
||||||
|
data object Rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ApprovalMode {
|
||||||
|
DENY
|
||||||
|
PROMPT
|
||||||
|
AUTO
|
||||||
|
YOLO
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSessionReducer ..|> SessionReducer
|
||||||
|
SessionProjector --> SessionReducer
|
||||||
|
SessionProjector ..|> Projection
|
||||||
|
|
||||||
|
DefaultSessionRepository --> EventReplayer
|
||||||
|
DefaultSessionRepository ..> Session : creates
|
||||||
|
Session *-- SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
package ":core:events" as EVENTS {
|
||||||
|
interface Projection
|
||||||
|
interface EventReplayer
|
||||||
|
interface EventStore
|
||||||
|
enum SessionStatus as SES
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSessionRepository --> EVENTS
|
||||||
|
SessionProjector --> EVENTS
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
@startuml
|
||||||
|
interface Tool
|
||||||
|
interface FileAffectingTool
|
||||||
|
interface ToolExecutor
|
||||||
|
interface ToolRegistry
|
||||||
|
interface ToolReducer
|
||||||
|
interface Projection
|
||||||
|
|
||||||
|
class DefaultToolReducer
|
||||||
|
class ToolProjector
|
||||||
|
class DefaultToolRepository
|
||||||
|
|
||||||
|
class ToolState
|
||||||
|
class ToolInvocationRecord
|
||||||
|
class ValidationResult
|
||||||
|
enum ToolInvocationStatus
|
||||||
|
enum ToolCapability
|
||||||
|
|
||||||
|
Tool <|-- FileAffectingTool
|
||||||
|
ToolReducer <|.. DefaultToolReducer
|
||||||
|
Projection <|.. ToolProjector
|
||||||
|
ToolProjector --> ToolReducer
|
||||||
|
DefaultToolRepository --> ToolProjector
|
||||||
|
DefaultToolRepository ..> ToolState
|
||||||
|
ToolState --> "invocations" ToolInvocationRecord
|
||||||
|
ToolInvocationRecord --> ToolInvocationStatus
|
||||||
|
|
||||||
|
package "core:events" <<External>> {
|
||||||
|
[ToolInvocationRequestedEvent]
|
||||||
|
[ToolExecutionStartedEvent]
|
||||||
|
[ToolExecutionCompletedEvent]
|
||||||
|
[ToolExecutionFailedEvent]
|
||||||
|
[ToolExecutionRejectedEvent]
|
||||||
|
[ToolRequest]
|
||||||
|
[ToolReceipt]
|
||||||
|
}
|
||||||
|
package "core:approvals" <<External>> {
|
||||||
|
[Tier]
|
||||||
|
}
|
||||||
|
package "core:sessions" <<External>> {
|
||||||
|
[Projection]
|
||||||
|
[EventReplayer]
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultToolReducer ..> "core:events"
|
||||||
|
DefaultToolRepository ..> "core:events"
|
||||||
|
DefaultToolRepository ..> "core:sessions"
|
||||||
|
Tool ..> "core:events"
|
||||||
|
Tool ..> "core:approvals"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
@startuml
|
||||||
|
package "core:transitions" {
|
||||||
|
data class WorkflowGraph {
|
||||||
|
+ id: String
|
||||||
|
+ stages: Map
|
||||||
|
+ transitions: Set
|
||||||
|
+ start: StageId
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StageConfig {
|
||||||
|
+ requiredCapabilities
|
||||||
|
+ tokenBudget
|
||||||
|
+ generationConfig
|
||||||
|
+ allowedTools
|
||||||
|
+ produces
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TransitionEdge {
|
||||||
|
+ id: TransitionId
|
||||||
|
+ from: StageId
|
||||||
|
+ to: StageId
|
||||||
|
+ condition: TransitionCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface TransitionCondition {
|
||||||
|
+ evaluate(EvaluationContext): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransitionResolver {
|
||||||
|
+ resolve(WorkflowGraph, EvaluationContext): TransitionDecision
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultTransitionResolver
|
||||||
|
|
||||||
|
sealed interface TransitionDecision {
|
||||||
|
data class Move
|
||||||
|
data object Stay
|
||||||
|
data class Blocked
|
||||||
|
data object NoMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EvaluationContext {
|
||||||
|
+ sessionId
|
||||||
|
+ currentStage
|
||||||
|
+ artifacts
|
||||||
|
+ variables
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface TransitionConditionEvaluator
|
||||||
|
fun interface PromptResolver
|
||||||
|
|
||||||
|
interface StageExecutor
|
||||||
|
interface StageExecutionEventMapper
|
||||||
|
|
||||||
|
data class StageExecutionRequest
|
||||||
|
sealed interface StageExecutionResult {
|
||||||
|
data class Success
|
||||||
|
data class Failure
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultTransitionResolver ..|> TransitionResolver
|
||||||
|
DefaultTransitionResolver --> TransitionConditionEvaluator
|
||||||
|
DefaultTransitionResolver --> TransitionOrdering
|
||||||
|
|
||||||
|
WorkflowGraph *-- StageConfig
|
||||||
|
WorkflowGraph *-- TransitionEdge
|
||||||
|
TransitionEdge *-- TransitionCondition
|
||||||
|
|
||||||
|
TransitionResolver --> TransitionDecision
|
||||||
|
TransitionResolver --> EvaluationContext
|
||||||
|
|
||||||
|
StageExecutionEventMapper --> StageExecutionRequest
|
||||||
|
StageExecutionEventMapper --> StageExecutionResult
|
||||||
|
StageExecutionEventMapper --> EventPayload
|
||||||
|
|
||||||
|
package "conditions" {
|
||||||
|
class AlwaysTrue
|
||||||
|
class VariableEquals
|
||||||
|
class ArtifactPresent
|
||||||
|
class ArtifactAbsent
|
||||||
|
class ArtifactValidated
|
||||||
|
class AllOf
|
||||||
|
class AnyOf
|
||||||
|
class Not
|
||||||
|
}
|
||||||
|
|
||||||
|
AlwaysTrue ..|> TransitionCondition
|
||||||
|
VariableEquals ..|> TransitionCondition
|
||||||
|
ArtifactPresent ..|> TransitionCondition
|
||||||
|
AllOf ..|> TransitionCondition
|
||||||
|
|
||||||
|
package "cycle detection" {
|
||||||
|
class CycleExtractor
|
||||||
|
class CycleDfs
|
||||||
|
class DeterministicAdjacencyBuilder
|
||||||
|
object CycleCanonicalizer
|
||||||
|
data class DetectedCycle
|
||||||
|
}
|
||||||
|
|
||||||
|
package "cycle policy" {
|
||||||
|
sealed interface CyclePolicy {
|
||||||
|
data class Retry
|
||||||
|
data class Refinement
|
||||||
|
data class Approval
|
||||||
|
}
|
||||||
|
data class CycleSignature
|
||||||
|
data class CyclePolicyBinding
|
||||||
|
class CyclePolicyResolver
|
||||||
|
class PolicyValidation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
@startuml
|
||||||
|
interface Validator
|
||||||
|
interface SemanticRule
|
||||||
|
|
||||||
|
class ValidationPipeline
|
||||||
|
class GraphValidator
|
||||||
|
class TransitionValidator
|
||||||
|
class SessionValidator
|
||||||
|
class SemanticValidator
|
||||||
|
class CyclePolicyBindingRule
|
||||||
|
class ArtifactPayloadValidator
|
||||||
|
class ApprovalTrigger
|
||||||
|
|
||||||
|
class ValidationOutcome
|
||||||
|
class ValidationReport
|
||||||
|
class ValidationSection
|
||||||
|
class ValidationIssue
|
||||||
|
class ValidationContext
|
||||||
|
|
||||||
|
package "core:transitions" <<External>> {
|
||||||
|
[WorkflowGraph]
|
||||||
|
[DetectedCycle]
|
||||||
|
[CyclePolicyBinding]
|
||||||
|
[TransitionEdge]
|
||||||
|
}
|
||||||
|
package "core:artifacts" <<External>> {
|
||||||
|
[FileWrittenArtifact]
|
||||||
|
[ProcessResultArtifact]
|
||||||
|
}
|
||||||
|
package "core:artifacts-store" <<External>> {
|
||||||
|
[ArtifactStore]
|
||||||
|
}
|
||||||
|
|
||||||
|
Validator <|.. GraphValidator
|
||||||
|
Validator <|.. TransitionValidator
|
||||||
|
Validator <|.. SessionValidator
|
||||||
|
Validator <|.. SemanticValidator
|
||||||
|
Validator <|.. ArtifactPayloadValidator
|
||||||
|
SemanticValidator --> "rules" SemanticRule
|
||||||
|
SemanticRule <|.. CyclePolicyBindingRule
|
||||||
|
ValidationPipeline --> "validators" Validator
|
||||||
|
ValidationPipeline --> ApprovalTrigger
|
||||||
|
ValidationPipeline ..> ValidationOutcome
|
||||||
|
ValidationOutcome --> ValidationReport
|
||||||
|
ValidationOutcome ..> ApprovalRequest
|
||||||
|
ValidationReport --> "sections" ValidationSection
|
||||||
|
ValidationSection --> "issues" ValidationIssue
|
||||||
|
ValidationIssue --> ValidationSeverity
|
||||||
|
ValidationIssue --> ValidationLocation
|
||||||
|
|
||||||
|
GraphValidator ..> "core:transitions"
|
||||||
|
TransitionValidator ..> "core:transitions"
|
||||||
|
SessionValidator ..> "core:sessions"
|
||||||
|
CyclePolicyBindingRule ..> "core:transitions"
|
||||||
|
ArtifactPayloadValidator ..> "core:artifacts"
|
||||||
|
ArtifactPayloadValidator ..> "core:artifacts-store"
|
||||||
|
ValidationContext ..> "core:transitions"
|
||||||
|
ValidationContext ..> "core:sessions"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-artifacts-cas" {
|
||||||
|
class CasArtifactStore
|
||||||
|
class CasConfig
|
||||||
|
interface ArtifactIndex
|
||||||
|
class SqliteArtifactIndex
|
||||||
|
class SegmentLayout
|
||||||
|
class SegmentWriter
|
||||||
|
class SegmentReader
|
||||||
|
class Location
|
||||||
|
class TailScanner
|
||||||
|
class LivenessScanner
|
||||||
|
class Compactor
|
||||||
|
class Evictor
|
||||||
|
class DefaultMaterializingArtifactWriter
|
||||||
|
}
|
||||||
|
interface ArtifactStore <<core:artifactstore>>
|
||||||
|
interface MaterializingArtifactWriter <<core:artifacts>>
|
||||||
|
ArtifactStore <|.. CasArtifactStore
|
||||||
|
MaterializingArtifactWriter <|.. DefaultMaterializingArtifactWriter
|
||||||
|
CasArtifactStore *--> CasConfig
|
||||||
|
CasArtifactStore *--> ArtifactIndex
|
||||||
|
CasArtifactStore *--> SegmentWriter
|
||||||
|
CasArtifactStore *--> SegmentReader
|
||||||
|
ArtifactIndex <|.. SqliteArtifactIndex
|
||||||
|
CasArtifactStore --> TailScanner : recover()
|
||||||
|
CasArtifactStore --> Compactor : compact()
|
||||||
|
CasArtifactStore --> Evictor : evict()
|
||||||
|
Compactor --> LivenessScanner
|
||||||
|
Evictor --> LivenessScanner
|
||||||
|
TailScanner --> ArtifactIndex : uses
|
||||||
|
TailScanner --> SegmentLayout : uses
|
||||||
|
SegmentWriter --> SegmentLayout : uses
|
||||||
|
SegmentReader --> SegmentLayout : uses
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-inference-commons" {
|
||||||
|
interface ModelManager
|
||||||
|
interface ManagedInferenceProvider
|
||||||
|
class ModelDescriptor
|
||||||
|
enum ResidencyMode
|
||||||
|
class ModelLoadException
|
||||||
|
}
|
||||||
|
interface InferenceProvider <<core:inference>>
|
||||||
|
ManagedInferenceProvider --|> InferenceProvider
|
||||||
|
ModelManager --> ModelDescriptor : load()
|
||||||
|
ManagedInferenceProvider --> ModelDescriptor : load()
|
||||||
|
ModelDescriptor --> ResidencyMode
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-inference-llama-cpp" {
|
||||||
|
class LlamaCppInferenceProvider
|
||||||
|
class DefaultModelManager
|
||||||
|
class DefaultManagedInferenceProvider
|
||||||
|
class LlamaProcess
|
||||||
|
class LlamaCppTokenizer
|
||||||
|
class GbnfGrammarConverter
|
||||||
|
package "LlamaCppApiModels" {
|
||||||
|
class ChatCompletionRequest
|
||||||
|
class ChatCompletionResponse
|
||||||
|
class ChatMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface InferenceProvider <<core:inference>>
|
||||||
|
interface ModelManager <<infra-inference-commons>>
|
||||||
|
interface ManagedInferenceProvider <<infra-inference-commons>>
|
||||||
|
InferenceProvider <|.. LlamaCppInferenceProvider
|
||||||
|
ManagedInferenceProvider <|.. DefaultManagedInferenceProvider
|
||||||
|
DefaultManagedInferenceProvider --> LlamaCppInferenceProvider : delegates to
|
||||||
|
ModelManager <|.. DefaultModelManager
|
||||||
|
DefaultModelManager *--> LlamaProcess : manages
|
||||||
|
LlamaCppInferenceProvider --> LlamaCppTokenizer : uses
|
||||||
|
LlamaCppInferenceProvider --> GbnfGrammarConverter : uses
|
||||||
|
DefaultModelManager --> LlamaCppInferenceProvider : creates
|
||||||
|
GbnfGrammarConverter --> ChatCompletionRequest : generates grammar for
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-persistence" {
|
||||||
|
class InMemoryEventStore
|
||||||
|
class SqliteEventStore
|
||||||
|
class LiveArtifactRepository
|
||||||
|
class JDBCHelper
|
||||||
|
}
|
||||||
|
interface EventStore <<core:events>>
|
||||||
|
ArtifactRepository <<core:artifacts>>
|
||||||
|
EventStore <|.. InMemoryEventStore
|
||||||
|
EventStore <|.. SqliteEventStore
|
||||||
|
ArtifactRepository <|.. LiveArtifactRepository
|
||||||
|
LiveArtifactRepository --> EventStore : subscribes
|
||||||
|
SqliteEventStore --> JDBCHelper : uses
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-scheduler" {
|
||||||
|
object Module
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-security" {
|
||||||
|
object Module
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-telemetry" {
|
||||||
|
object Module
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-tools-filesystem" {
|
||||||
|
class FileReadTool
|
||||||
|
class FileWriteTool
|
||||||
|
class FileEditTool
|
||||||
|
}
|
||||||
|
interface Tool <<core:tools>>
|
||||||
|
interface ToolExecutor <<core:tools>>
|
||||||
|
interface FileAffectingTool <<core:tools>>
|
||||||
|
Tool <|.. FileReadTool
|
||||||
|
Tool <|.. FileWriteTool
|
||||||
|
Tool <|.. FileEditTool
|
||||||
|
ToolExecutor <|.. FileReadTool
|
||||||
|
ToolExecutor <|.. FileWriteTool
|
||||||
|
ToolExecutor <|.. FileEditTool
|
||||||
|
FileAffectingTool <|.. FileWriteTool
|
||||||
|
FileAffectingTool <|.. FileEditTool
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-tools" {
|
||||||
|
class SandboxedToolExecutor
|
||||||
|
class DispatchingToolExecutor
|
||||||
|
class DefaultToolRegistry
|
||||||
|
class ShellTool
|
||||||
|
class DiffUtil
|
||||||
|
class ToolConfig
|
||||||
|
class ShellConfig
|
||||||
|
class FileReadConfig
|
||||||
|
class FileWriteConfig
|
||||||
|
class FileEditConfig
|
||||||
|
}
|
||||||
|
interface ToolExecutor <<core:tools>>
|
||||||
|
interface ToolRegistry <<core:tools>>
|
||||||
|
interface Tool <<core:tools>>
|
||||||
|
ToolExecutor <|.. SandboxedToolExecutor
|
||||||
|
ToolExecutor <|.. DispatchingToolExecutor
|
||||||
|
ToolExecutor <|.. ShellTool
|
||||||
|
ToolRegistry <|.. DefaultToolRegistry
|
||||||
|
Tool <|.. ShellTool
|
||||||
|
SandboxedToolExecutor --> ToolRegistry : resolves
|
||||||
|
SandboxedToolExecutor --> ToolExecutor : delegates to
|
||||||
|
DispatchingToolExecutor --> ToolRegistry : resolves
|
||||||
|
SandboxedToolExecutor --> DiffUtil : uses
|
||||||
|
ToolConfig --> ShellConfig
|
||||||
|
ToolConfig --> FileReadConfig
|
||||||
|
ToolConfig --> FileWriteConfig
|
||||||
|
ToolConfig --> FileEditConfig
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@startuml
|
||||||
|
!theme plain
|
||||||
|
package "infra-workflow" {
|
||||||
|
interface WorkflowLoader
|
||||||
|
class TomlWorkflowLoader
|
||||||
|
class ConditionSpec
|
||||||
|
class ConditionFactory
|
||||||
|
interface PromptLoader
|
||||||
|
class FileSystemPromptLoader
|
||||||
|
class PromptNotFoundException
|
||||||
|
class WorkflowValidationException
|
||||||
|
}
|
||||||
|
interface WorkflowGraph <<core:transitions>>
|
||||||
|
WorkflowLoader --> WorkflowGraph : load()
|
||||||
|
WorkflowLoader <|.. TomlWorkflowLoader
|
||||||
|
TomlWorkflowLoader --> ConditionSpec : parses
|
||||||
|
TomlWorkflowLoader --> ConditionFactory : uses
|
||||||
|
ConditionFactory --> ConditionSpec : converts
|
||||||
|
PromptLoader <|.. FileSystemPromptLoader
|
||||||
|
TommWorkflowLoader --> WorkflowValidationException : throws
|
||||||
|
FileSystemPromptLoader --> PromptNotFoundException : throws
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
note "No source files" as N
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@startuml
|
||||||
|
package "test-approvals" {
|
||||||
|
[TierImmutabilityTest]
|
||||||
|
[GrantSemanticsTest]
|
||||||
|
[ModeApprovalTest]
|
||||||
|
[ApprovalEngineEdgeCasesTest]
|
||||||
|
[NoExecutionCouplingTest]
|
||||||
|
[ApprovalTriggerTest]
|
||||||
|
}
|
||||||
|
|
||||||
|
[TierImmutabilityTest] --> [Tier]
|
||||||
|
[GrantSemanticsTest] --> [DefaultApprovalEngine]
|
||||||
|
[ModeApprovalTest] --> [DefaultApprovalEngine]
|
||||||
|
[ApprovalEngineEdgeCasesTest] --> [DefaultApprovalEngine]
|
||||||
|
[NoExecutionCouplingTest] --> [DefaultApprovalEngine]
|
||||||
|
[ApprovalTriggerTest] --> [ApprovalTrigger]
|
||||||
|
|
||||||
|
[Tests] --> [ApprovalFixtures] : test data
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@startuml
|
||||||
|
[Contract Tests] -> [Domain Types] : construct
|
||||||
|
[Contract Tests] -> [kotlinx.serialization] : encode/decode
|
||||||
|
[Domain Types] --> [Contract Tests] : value
|
||||||
|
[Contract Tests] --> [Assertions] : verify contract
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@startuml
|
||||||
|
package "test-deterministic" {
|
||||||
|
[SessionReplayDeterminismTest]
|
||||||
|
[ContextCompressionDeterminismTest]
|
||||||
|
[ApprovalEngineDeterminismTest]
|
||||||
|
[RouterProjectorTest]
|
||||||
|
[RouterFacadeTest]
|
||||||
|
[GraphValidatorTest]
|
||||||
|
[ValidationPipelineShortCircuitTest]
|
||||||
|
[DefaultContextPackBuilderTest]
|
||||||
|
[TokenBudgetEnforcementTest]
|
||||||
|
}
|
||||||
|
|
||||||
|
[SessionReplayDeterminismTest] --> [EventReplayer] : replay N times
|
||||||
|
[ContextCompressionDeterminismTest] --> [ContextCompressor] : compress N times
|
||||||
|
[ApprovalEngineDeterminismTest] --> [ApprovalEngine] : decide N times
|
||||||
|
|
||||||
|
note right of [SessionReplayDeterminismTest]
|
||||||
|
assert: state == state == state
|
||||||
|
end note
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@startuml
|
||||||
|
package "test-fixtures" {
|
||||||
|
[EventFixtures]
|
||||||
|
[InferenceFixtures]
|
||||||
|
[ApprovalFixtures]
|
||||||
|
[WorkflowFixtures]
|
||||||
|
[TransitionFixtures]
|
||||||
|
[CycleFixtures]
|
||||||
|
[ContextFixtures]
|
||||||
|
[MockInferenceProvider]
|
||||||
|
[MockTokenizer]
|
||||||
|
[EchoTool]
|
||||||
|
[FailingTool]
|
||||||
|
[RejectedTool]
|
||||||
|
[MockEventReplayer]
|
||||||
|
[MockSessionRepository]
|
||||||
|
}
|
||||||
|
|
||||||
|
[Testing Modules] --> [test-fixtures] : reuse in tests
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
@startuml
|
||||||
|
[SessionOrchestratorIntegrationTest] -> [DefaultSessionOrchestrator] : run(sessionId, graph, config)
|
||||||
|
[DefaultSessionOrchestrator] --> [EventStore] : append events
|
||||||
|
[DefaultSessionOrchestrator] --> [MockEngines] : resolve/infer/validate/approve
|
||||||
|
[Test] <- [EventStore] : read events
|
||||||
|
[Test] <- [SessionRepository] : get session state
|
||||||
|
|
||||||
|
[ValidationPipelineIntegrationTest] -> [ValidationPipeline] : validate(payload)
|
||||||
|
[ValidationPipeline] -> [MockValidator1] : validate
|
||||||
|
[ValidationPipeline] -> [MockValidator2] : validate
|
||||||
|
[MockValidator*] --> [ValidationPipeline] : ValidationSection
|
||||||
|
[Test] <- [ValidationPipeline] : ValidationReport
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@startuml
|
||||||
|
package "test-kernel" {
|
||||||
|
[DefaultRetryCoordinatorTest]
|
||||||
|
[ReplayInferenceProviderTest]
|
||||||
|
[MockOrchestrationEventReplayer]
|
||||||
|
[MockSessionEventReplayer]
|
||||||
|
}
|
||||||
|
|
||||||
|
[DefaultRetryCoordinatorTest] --> [DefaultRetryCoordinator]
|
||||||
|
[ReplayInferenceProviderTest] --> [InferenceProjector]
|
||||||
|
[ReplayInferenceProviderTest] --> [MockOrchestrationEventReplayer]
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@startuml
|
||||||
|
[Test] -> [EventFixtures] : create event sequence
|
||||||
|
[Test] -> [Projection] : project(events)
|
||||||
|
[Projection] -> [Reducer] : reduce(state, event)
|
||||||
|
[Reducer] --> [Projection] : new state
|
||||||
|
[Projection] --> [Test] : final state
|
||||||
|
[Test] --> [Assertions] : verify fields
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@startuml
|
||||||
|
[Test] -> [EventFixtures] : create events
|
||||||
|
[Test] -> [InMemoryEventStore] : append events
|
||||||
|
[Test] -> [EventReplayer] : rebuild(sessionId)
|
||||||
|
[EventReplayer] --> [Projection] : reduce events
|
||||||
|
[Projection] --> [EventReplayer] : final state
|
||||||
|
[Test] <-- [EventReplayer] : assert state matches expectation
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@startuml
|
||||||
|
[Test] -> [WorkflowFixtures] : graph
|
||||||
|
[Test] -> [TransitionFixtures] : condition evaluator
|
||||||
|
[Test] -> [DefaultTransitionResolver] : resolve(from, graph)
|
||||||
|
[DefaultTransitionResolver] --> [Test] : next stage
|
||||||
|
[Test] --> [Assertions]
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
= app-cli
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Command-line interface built with Clikt that connects to the Correx server to start, manage, and observe sessions. Designed for scripted/headless usage and non-interactive environments.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Start sessions by workflow ID via HTTP POST to the server
|
||||||
|
* Attach to a session's WebSocket stream and relay lifecycle events (session, stage, tool, approval) to stdout
|
||||||
|
* Prompt for approval decisions (approve / reject / steer) when a TTY is available
|
||||||
|
* Support `--json` output for machine parsing and `--quiet` for error-only mode
|
||||||
|
* List sessions, resume/cancel sessions, fetch session events
|
||||||
|
* List inference providers and their health status
|
||||||
|
* Send approval decisions to the server via HTTP
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not maintain persistent state or local event cache
|
||||||
|
* Does not contain domain orchestration logic
|
||||||
|
* Does not perform inference or tool execution
|
||||||
|
* Does not implement the approval engine — only forwards user decisions
|
||||||
|
* Does not run a server
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== CorrexCli
|
||||||
|
* **kind**: `CliktCommand` (root)
|
||||||
|
* **purpose**: Top-level CLI command. Defines global flags `--json` and `--quiet`.
|
||||||
|
|
||||||
|
=== RunCommand
|
||||||
|
* **kind**: `CliktCommand` (subcommand)
|
||||||
|
* **purpose**: Starts a session for a workflow and streams events to stdout. Supports `--workflow`, `--session`, `--auto-approve`, `--host`, `--port`.
|
||||||
|
|
||||||
|
=== CliWsClient
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thin Ktor WebSocket client wrapping `HttpClient.webSocket`. Calls `onMessage` callback per frame; returns `false` from callback to close the stream.
|
||||||
|
|
||||||
|
=== SystemExitException
|
||||||
|
* **kind**: exception
|
||||||
|
* **purpose**: Propagates exit codes (0 = success, 1 = session failure, 2 = rejection in non-TTY mode).
|
||||||
|
|
||||||
|
=== RunContext / ApprovalContext
|
||||||
|
* **kind**: internal data classes
|
||||||
|
* **purpose**: Carry session ID, JSON flag, auto-approve flag, TTY status through the message handling loop.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* WebSocket frames from server encoded as JSON `ServerMessage` variants. The CLI decodes the `type` field from a raw `JsonObject` (not deserialized via `ProtocolSerializer`).
|
||||||
|
|
||||||
|
*outbound:* `ApprovalResponsePayload` sent as text frames on the WebSocket session.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `apps:cli` depends only on external libraries: Clikt (CLI framework) and Ktor client (HTTP + WebSockets)
|
||||||
|
* Talks to the server at `http://{host}:{port}` via REST and `ws://{host}:{port}/sessions/{id}/stream`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `CliWsClient.sessionStream` breaks out of the frame loop when `onMessage` returns `false`
|
||||||
|
* Auto-approve without a TTY is impossible — if `--auto-approve` is not set and no TTY, all approvals are rejected
|
||||||
|
* Exit code 2 means the user rejected an approval in non-interactive mode
|
||||||
|
* `RunCommand` creates one session and one WebSocket connection per invocation
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, app-cli, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/app-cli.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* JSON output format is hand-constructed in `printLine()` rather than using a serializer — field ordering and escaping may diverge from `ServerMessage` serialization
|
||||||
|
* `--session` (resume) acceptance on the server side is not yet implemented — the parameter is forwarded but the server's response is untested
|
||||||
|
* `resolveDecision` blocks on stdin in TTY mode — no timeout mechanism for user input
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should session resume reconnect to an existing WebSocket stream or replay from the event store? Currently the parameter is accepted but behavior is undefined.
|
||||||
|
* Should `RunCommand` support multiple sessions concurrently (e.g., `--watch` mode)?
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
= app-desktop
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder application module for a future desktop GUI application. Currently contains only a stub `main()` function that prints its identity and exits.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure and build configuration for a future desktop application
|
||||||
|
* Prints a startup banner to stdout
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not create any windows, UI components, or graphical interface
|
||||||
|
* Does not contain any domain logic or infrastructure wiring
|
||||||
|
* Does not connect to any Correx server
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Main.kt
|
||||||
|
* **kind**: top-level `main()` function
|
||||||
|
* **purpose**: Placeholder entry point. Prints `"correx :: apps/desktop"` and exits.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. The module has no event interaction.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* Depends only on `com.github.ajalt.clikt:clikt` in build configuration (no actual usage in source)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* The module has no runtime state, invariants, or behavioral contracts beyond printing to stdout
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, app-desktop, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/app-desktop.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Not implemented — this is a structural placeholder with no functional code
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will the desktop app be built with Compose Multiplatform, JavaFX, or another UI toolkit?
|
||||||
|
* Should the desktop app embed the Correx server, or connect to an existing server instance?
|
||||||
|
* What is the relationship between desktop app and TUI — do they share components or are they independent implementations?
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
= app-server
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Ktor-based HTTP + WebSocket server that hosts the Correx orchestration runtime. Wires together core domain modules with infrastructure adapters to run, observe, and manage agentic workflow sessions.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Expose REST endpoints for session lifecycle (create, get, cancel, list events)
|
||||||
|
* Expose REST endpoints for workflow discovery and provider health
|
||||||
|
* Maintain long-lived WebSocket connections for per-session and global event streaming
|
||||||
|
* Bridge domain events (`StoredEvent`) to wire protocol messages (`ServerMessage`)
|
||||||
|
* Coordinate approval workflows: register WS clients, receive decisions, forward to orchestration
|
||||||
|
* Log all emitted events via `LoggingEventStore`
|
||||||
|
* Initialize and wire the full dependency graph: event store, repositories, engines, registries
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not perform inference — delegates to `core:inference` via `InferenceRouter`
|
||||||
|
* Does not execute tools — delegates to `infrastructure:tools` via `ToolExecutor`
|
||||||
|
* Does not persist state beyond what the event store and artifact store provide
|
||||||
|
* Does not handle TLS termination or authentication (no security middleware)
|
||||||
|
* Does not provide horizontal scaling or clustering
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== ServerModule
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Top-level wiring container holding all server-scoped dependencies (orchestrator, event store, registries, repositories, coordinator). Creates an `ApprovalCoordinator` and subscribes to `ApprovalRequestedEvent` on `start()`.
|
||||||
|
* **fields**: `moduleScope: CoroutineScope` — SupervisorJob-backed scope owned by the module; hosts the event subscription and approval timeout jobs.
|
||||||
|
|
||||||
|
=== Application.configureServer(module)
|
||||||
|
* **kind**: extension function
|
||||||
|
* **purpose**: Installs Ktor plugins (CallLogging, WebSockets, ContentNegotiation, StatusPages) and defines routing tree.
|
||||||
|
|
||||||
|
=== GlobalStreamHandler
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Handles `/stream` WebSocket connections. Sends initial snapshot (provider health + workflow list), then streams all events from `eventStore.subscribeAll()` via `SessionEventBridge` + `DomainEventMapper`.
|
||||||
|
|
||||||
|
=== SessionStreamHandler
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Handles `/sessions/{id}/stream` WebSocket connections. Replays events from optionally specified `lastEventId` cursor, then forwards incoming client messages (approval, cancel, chat input, ping) to the appropriate module component.
|
||||||
|
|
||||||
|
=== DomainEventMapper
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Maps domain `StoredEvent.payload` variants to protocol `ServerMessage` variants for WebSocket transmission.
|
||||||
|
|
||||||
|
=== SessionEventBridge
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Reconstructs snapshot state (orchestration status, pending approvals, recent events) for all active sessions from the event store. Used during global stream initialization.
|
||||||
|
|
||||||
|
=== ApprovalCoordinator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Manages approval request routing: registers session and global WS clients, broadcasts `ApprovalRequired` messages, schedules timeout-based rejection, forwards `ApprovalResponse` decisions to the orchestrator.
|
||||||
|
|
||||||
|
=== ApprovalResponseMapper (internal)
|
||||||
|
* **kind**: extension function
|
||||||
|
* **purpose**: Translates `ClientMessage.ApprovalResponse` to domain `ApprovalDecision`.
|
||||||
|
|
||||||
|
=== LoggingEventStore
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Decorator over `EventStore` that logs all `append`/`appendAll` calls with session ID, event type, and outcome.
|
||||||
|
|
||||||
|
=== ProviderRegistry
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Thin server-side adapter over `DefaultProviderRegistry` — exposes `listAll()` and `healthCheckAll()`.
|
||||||
|
|
||||||
|
=== WorkflowRegistry
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Abstraction for discovering and loading `WorkflowGraph` definitions by ID.
|
||||||
|
|
||||||
|
=== FileSystemWorkflowRegistry
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Implements `WorkflowRegistry` by scanning `~/.config/correx/workflows/*.toml` lazily. Caches loaded graphs.
|
||||||
|
|
||||||
|
=== ServerMessage / ClientMessage / Dtos
|
||||||
|
* **kind**: sealed interfaces / data classes
|
||||||
|
* **purpose**: Wire protocol types. `ServerMessage` has two marker subtypes: `SessionMessage` (event-derived, carries sequence cursors) and `NonEventMessage` (control messages, no cursors).
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* REST requests (POST/DELETE sessions, GET status/workflows/providers) and WebSocket `ClientMessage` frames (approval responses, cancel, chat input, ping).
|
||||||
|
|
||||||
|
*outbound:* WebSocket `ServerMessage` frames from `DomainEventMapper` (session lifecycle, stage lifecycle, tool lifecycle, inference lifecycle, approval requests). Also HTTP JSON responses for REST endpoints.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:kernel` — `DefaultSessionOrchestrator` for running sessions, `ApprovalGateway` for injecting decisions
|
||||||
|
* `:core:events` — `EventStore`, `StoredEvent`, all event payload types
|
||||||
|
* `:core:approvals` — `DefaultApprovalEngine`, `Tier`, domain approval types
|
||||||
|
* `:core:sessions` — `DefaultSessionRepository`, `DefaultSessionReducer`, `SessionProjector`
|
||||||
|
* `:core:inference` — `DefaultInferenceRouter`, `InferenceProjector`, `InferenceRepository`
|
||||||
|
* `:core:transitions` — `DefaultTransitionResolver`, `WorkflowGraph`
|
||||||
|
* `:core:context` — `DefaultContextPackBuilder`, `DefaultContextCompressor`
|
||||||
|
* `:core:validation` — `ValidationPipeline`, `ArtifactPayloadValidator`
|
||||||
|
* `:core:risk` — `DefaultRiskAssessor`
|
||||||
|
* `:core:router` — `RouterFacade`, `RouterConfig`
|
||||||
|
* `:core:tools` — `ToolRegistry`
|
||||||
|
* `:core:config` — `ConfigLoader`, `ApprovalConfig`
|
||||||
|
* `:infrastructure:*` — providers, tool executor, workflow loader, artifact repository, approval repository, persistence
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ServerModule.start()` is idempotent — calling it twice is a no-op for the second call (`subscriptionJob` guard)
|
||||||
|
* Event subscription is established BEFORE `replaySnapshot()` reads `lastGlobalSequence` to prevent missed events
|
||||||
|
* Orchestrator is launched in `module.moduleScope` after protocol messages are delivered (ensures no silent data loss on disconnected WS)
|
||||||
|
* Approval timeout is scheduled per-request; `resolved` map prevents double-resolution
|
||||||
|
* `DomainEventMapper` returns `null` for unmapped payload types, which are silently dropped from the stream
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, app-server, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/app-server.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `GET /sessions` returns `emptyList()` unconditionally — no server-side session listing is implemented
|
||||||
|
* `SessionStreamHandler` does not handle `ClientMessage.ResumeSession` — it returns a `ProtocolError`
|
||||||
|
* `DomainEventMapper` defaults to `NoopArtifactStore`, but all production call sites (`GlobalStreamHandler`, `SessionStreamHandler`) inject `module.artifactStore` — the `NoopArtifactStore` path is only reachable if a caller omits the argument
|
||||||
|
* `RiskSummaryDto` is hardcoded to `"unknown"` in `mapApprovalRequested` — the approval event does not carry risk information
|
||||||
|
* Session IDs are generated server-side as random UUIDs; client-provided `sessionId` in `StartSessionRequest` is accepted but not used
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should the server support session listing by filtering on status or workflow ID?
|
||||||
|
* Should `RiskSummary` data be attached to `ApprovalRequestedEvent` payload, or should the server query it separately?
|
||||||
|
* Is the global stream (`/stream`) scaling strategy adequate for N concurrent clients? The `Channel` buffer size is fixed at 1024.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
= app-tui
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Terminal UI built on the Tamboulib TUI framework that provides a real-time interactive dashboard for monitoring and controlling Correx sessions.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Display a live session list with status, workflow name, and background-update badges
|
||||||
|
* Show workflow definitions available on the server for starting new sessions
|
||||||
|
* Stream session lifecycle events (stage, tool, inference) in a scrollable event strip
|
||||||
|
* Display tool diffs when tool executions complete with file modifications
|
||||||
|
* Surface approval requests with tool name, tier, risk summary, and command preview
|
||||||
|
* Accept approval decisions (approve / reject) and steering notes via keyboard input
|
||||||
|
* Support session filtering by workflow name, input history navigation, and keyboard-driven UI modes
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not contain any domain orchestration or event-sourcing logic
|
||||||
|
* Does not persist any state — all state is in-memory and rebuilt on reconnect
|
||||||
|
* Does not authenticate or encrypt connections
|
||||||
|
* Does not support mouse interaction
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== TuiState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Root application state. Contains `connection`, `sessions`, `input`, `provider` sub-states plus display metadata (cursor position, input buffer, history, snapshot phase flags, per-session cursors).
|
||||||
|
|
||||||
|
=== SessionsState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Holds the list of `SessionSummary` objects, filter text, session/workflow selection, and background update counter.
|
||||||
|
|
||||||
|
=== DisplayState
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: Tri-state enum (`IDLE`, `IN_SESSION`, `APPROVAL`) that drives which layout the TUI renders.
|
||||||
|
|
||||||
|
=== SessionSummary
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Projected view of a session for display — ID, status, workflow, stage, tools, recent events, pending approval.
|
||||||
|
|
||||||
|
=== TuiWsClient
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Ktor WebSocket client that maintains a persistent global stream (`/stream`). Exposes cold `Flow<ServerMessage>` and `Flow<ConnectionEvent>` backed by `Channel.UNLIMITED`. Implements exponential backoff reconnection.
|
||||||
|
|
||||||
|
=== Action (sealed interface)
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Union of all user input and system events (navigation, input editing, approval, server messages, connection events).
|
||||||
|
|
||||||
|
=== KeyEvent (sealed class)
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: Normalized key events independent of the TUI backend (TambouiKeyEvent mapping).
|
||||||
|
|
||||||
|
=== RootReducer
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Top-level reducer that coordinates `SnapshotPhaseReducer` + four sub-reducers (`InputReducer`, `SessionsReducer`, `ConnectionReducer`, `ProviderReducer`). Handles cross-reducer state weaving (input history, approval dismissal, auto-enter on session start).
|
||||||
|
|
||||||
|
=== SnapshotPhaseReducer
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Manages the snapshot-to-live transition. Buffers event-bearing messages during snapshot replay; drains buffered events atomically when `SnapshotComplete` arrives; deduplicates live events by `sessionSequence`.
|
||||||
|
|
||||||
|
=== Effect (sealed interface)
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Side-effect descriptors produced by reducers — either `SendWs(ClientMessage)` or `Quit`.
|
||||||
|
|
||||||
|
=== EffectDispatcher
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Executes effects sequentially. Dispatches WS messages via `TuiWsClient.send()` and `Quit` via a callback that exits the TUI runner.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* `ServerMessage` frames from the global WebSocket stream, decoded by `TuiWsClient` and wrapped in `Action.ServerEventReceived`. Also connection lifecycle events (`Connected`, `Disconnected`, `RetryScheduled`).
|
||||||
|
|
||||||
|
*outbound:* `ClientMessage` frames sent via `Effect.SendWs` — `StartSession`, `CancelSession`, `ApprovalResponse`.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:apps:server` — imports `ServerMessage`, `ClientMessage`, `ApprovalDecision`, `PauseReason`, `WorkflowDto`, `ProviderHealthDto`
|
||||||
|
* `:core:events` — imports `ApprovalRequestId`, `SessionId`
|
||||||
|
* `:core:approvals` — imports `Tier`
|
||||||
|
|
||||||
|
Tamboulib framework: `tamboui-tui`, `tamboui-widgets`, `tamboui-core`, `tamboui-jline3-backend`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ws.messages` and `ws.connection` are single-consumer cold flows — adding a second collector drops messages from one
|
||||||
|
* Effects are dispatched sequentially (dispatchAll) — order matches `RootReducer` concatenation
|
||||||
|
* `Effect.Quit` is always appended last by `RootReducer.reduce`
|
||||||
|
* Snapshot phase is entered initially and on every `Disconnected`; buffered events are drained atomically at `SnapshotComplete`
|
||||||
|
* Live event deduplication uses per-session `sessionSequence` cursor; gaps are warned and applied anyway
|
||||||
|
* The TUI creates one WS connection (`/stream`) — per-session streams are not used
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, app-tui, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/app-tui.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `StageToolManifest` arriving before `SessionStarted` is silently dropped (TODO RF-3) — the session won't exist in the list yet
|
||||||
|
* Chat submission in `IN_SESSION` is blocked (returns empty effects) — router integration is deferred to Epic 14
|
||||||
|
* `InputHistory` limit is hardcoded to 50 entries per session
|
||||||
|
* Event strip shows at most 5 events for the selected session
|
||||||
|
* Session list is capped at 7 visible entries; overflow is shown as a count but not scrollable
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should the TUI support per-session WebSocket streams instead of relying solely on the global stream?
|
||||||
|
* Should approval decisions support freeform steering note input that can exceed one line?
|
||||||
|
* How should workflow picker handle very large numbers of available workflows (10+)?
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
= app-worker
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder application module for a long-running background worker process. Currently contains only a stub `main()` function that prints its identity and exits.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure and build configuration for a future worker process
|
||||||
|
* Prints a startup banner to stdout
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not perform any background processing, job queue management, or event consumption
|
||||||
|
* Does not contain any domain logic or infrastructure wiring
|
||||||
|
* Does not connect to any Correx server or event store
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Main.kt
|
||||||
|
* **kind**: top-level `main()` function
|
||||||
|
* **purpose**: Placeholder entry point. Prints `"correx :: apps/worker"` and exits.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. The module has no event interaction.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* Depends only on `com.github.ajalt.clikt:clikt` in build configuration (no actual usage in source)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* The module has no runtime state, invariants, or behavioral contracts beyond printing to stdout
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, app-worker, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/app-worker.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Not implemented — this is a structural placeholder with no functional code
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will the worker process handle queued session execution, inference batching, or tool dispatch at scale?
|
||||||
|
* Should the worker connect to the server via the event store subscription mechanism or a separate message queue?
|
||||||
|
* Does the worker need its own `ServerModule`-style wiring, or will it operate as a stateless consumer?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= core-agents
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
This module has no source files. It exists as a buildable Gradle module with a `build.gradle` file but no Kotlin source code in the `src/main/kotlin` tree. It is a placeholder for future agent-related functionality.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* None (no source files)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Everything (no source files)
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
No types exist in this module.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
This module does not interact with events.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No dependencies (empty build.gradle with no `dependencies` block beyond the default plugins).
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
n/a
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-agents, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-agents.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* The module compiles but produces no classes. If it is referenced as a dependency by another module, that module will fail to resolve imports.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* What functionality is this module intended to contain? (Agent definitions, agent lifecycle, agent orchestration?)
|
||||||
|
* Should it be removed or marked explicitly as pending?
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
= core-approvals
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Evaluates whether a proposed operation should proceed by checking active grants and session approval mode, then returning an approval decision (auto-approved or pending human review). Tracks requests, decisions, and grants as event-sourced state.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Approving or rejecting operations based on tier, grants, and approval mode
|
||||||
|
* Tracking pending approval requests and resolved decisions
|
||||||
|
* Recording active grants (time-bounded permissions scoped to session/stage/project)
|
||||||
|
* Rebuilding approval state from the event log
|
||||||
|
* Providing a no-op engine for deterministic replay
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not enforce policy — policy is absolute upstream
|
||||||
|
* Does not emit events — all events are consumed from the event store
|
||||||
|
* Does not validate whether the operation itself is valid (delegated to validation)
|
||||||
|
* Does not determine execution tiers — tiers are declared by tools
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== ApprovalReducer
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Transforms approval state given a stored event.
|
||||||
|
|
||||||
|
=== DefaultApprovalReducer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Handles `ApprovalRequestedEvent`, `ApprovalDecisionResolvedEvent`, `ApprovalGrantCreatedEvent`, `ApprovalGrantExpiredEvent`.
|
||||||
|
|
||||||
|
=== ApprovalProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `ApprovalReducer` as a `Projection<ApprovalState>`.
|
||||||
|
|
||||||
|
=== DefaultApprovalRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Rebuilds `ApprovalState` for a session via `EventReplayer`.
|
||||||
|
|
||||||
|
=== ApprovalState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: In-memory projection of all approval-state for a session.
|
||||||
|
* **fields**: `requests`, `decisions`, `grants`
|
||||||
|
|
||||||
|
=== DomainApprovalRequest
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A request for approval of a tiered operation.
|
||||||
|
* **fields**: `id` (ApprovalRequestId), `tier`, `validationReportId`, `riskSummaryId`, `timestamp`, `causationId`, `correlationId`, `userSteering`, `toolName`, `preview`
|
||||||
|
|
||||||
|
=== ApprovalDecision
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: The outcome of an approval evaluation.
|
||||||
|
* **fields**: `outcome` (ApprovalOutcome), `state` (ApprovalStatus), `tier`, `contextSnapshot`, `reason`, `userSteering`
|
||||||
|
|
||||||
|
=== ApprovalGrant
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A time-bounded permission that bypasses approval for matching operations.
|
||||||
|
* **fields**: `id` (GrantId), `scope` (GrantScope), `permittedTiers`, `reason`, `timestamp`, `expiresAt`
|
||||||
|
|
||||||
|
=== ApprovalContext
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Snapshot of identity, mode, and causality chain at decision time.
|
||||||
|
* **fields**: `identity` (ApprovalScopeIdentity), `mode` (ApprovalMode), `causationId`, `correlationId`
|
||||||
|
|
||||||
|
=== ApprovalScopeIdentity
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Identifies the scope (session, stage, project) a decision applies to.
|
||||||
|
|
||||||
|
=== ApprovalEngine
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Evaluates a request against grants and approval mode to produce a decision.
|
||||||
|
|
||||||
|
=== DefaultApprovalEngine
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Implements the tier-based approval logic. Checks grants first, then falls through to mode-based thresholds. Returns `PENDING` for requests above the approval threshold.
|
||||||
|
|
||||||
|
=== NoOpApprovalEngine
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Always returns `AUTO_APPROVED` with no logic. Used during replay to avoid live approval evaluation.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `ApprovalRequestedEvent` → creates a `DomainApprovalRequest` in state
|
||||||
|
* `ApprovalDecisionResolvedEvent` → creates an `ApprovalDecision` in state
|
||||||
|
* `ApprovalGrantCreatedEvent` → creates an `ApprovalGrant` in state
|
||||||
|
* `ApprovalGrantExpiredEvent` → removes the grant from state
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. This module is purely a projection — events are emitted by the orchestrator and consumed here.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, event payload types, shared identity types (`SessionId`, `ApprovalRequestId`, etc.)
|
||||||
|
* `:core:sessions` — `Projection`, `EventReplayer`, `ApprovalMode`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ApprovalDecision.isFinal` is true only when `state != PENDING`
|
||||||
|
* `ApprovalDecision.isApproved` is true only when outcome is `APPROVED` or `AUTO_APPROVED`
|
||||||
|
* A grant with `expiresAt <= now` is never matched by `DefaultApprovalEngine`
|
||||||
|
* `NoOpApprovalEngine` is used exclusively on the replay path — never during live execution
|
||||||
|
* Request, decision, and grant IDs are globally unique; maps in `ApprovalState` are indexed by these IDs
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-approvals, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-approvals.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ApprovalDecision.id` is nullable in the data class but `DefaultApprovalReducer` always assigns a non-null ID; `NoOpApprovalEngine` returns `null` for the ID, which could make downstream consumers handle both cases unnecessarily.
|
||||||
|
* `ApprovalContext` construction in `DefaultApprovalReducer` hardcodes `ApprovalMode.PROMPT` and null stage/project IDs because the event does not carry this information. The full context is only available at evaluation time.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `ApprovalContext` be embedded in the event payload rather than reconstructed during reduction?
|
||||||
|
* The `RiskSummaryId` on `DomainApprovalRequest` references data from the validation module — the two modules are separate and share no common risk model type.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
= core-artifacts-store
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines the interface for binary blob storage of artifact payloads. Provides content-addressable put/get semantics and a flush-before-commit pattern for transactional consistency. It is the storage backend for artifact content, separate from artifact lifecycle metadata.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Storing binary artifact payloads with content-addressable IDs
|
||||||
|
* Retrieving binary artifact payloads by ID
|
||||||
|
* Supporting a transactional flush pattern: `flushBefore(commit)` ensures writes are durable before the commit action executes
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not manage artifact lifecycle or metadata — those are owned by `:core:artifacts`
|
||||||
|
* Does not validate artifact payloads — validation is handled by `:core:validation`
|
||||||
|
* Does not implement the storage — this is an interface; implementations live in `:infrastructure:*`
|
||||||
|
* Does not track artifact provenance, lineage, or relationships
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== ArtifactStore
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Binary blob storage for artifact payloads.
|
||||||
|
* **fields**: none
|
||||||
|
* **methods**: `put(bytes): ArtifactId`, `get(id): ByteArray?`, `flushBefore(commit)`
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
This module does not interact with events. It is a pure storage interface with no event-sourced state.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `ArtifactId` (used as the content-addressed key)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ArtifactStore.put()` returns the same `ArtifactId` for the same bytes (content-addressable)
|
||||||
|
* `ArtifactStore.flushBefore(commit)` guarantees that all prior `put()` calls are durable before `commit` executes
|
||||||
|
* No event is emitted for storage operations — storage is a pure side-effect layer
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-artifacts-store, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-artifacts-store.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Only one source file exists (`ArtifactStore.kt`). There are no implementations, no default behaviour, and no in-memory fallback.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should there be a default in-memory implementation for testing?
|
||||||
|
* Is `flushBefore(commit)` sufficient for all transactional patterns, or is a full begin/commit/rollback API needed?
|
||||||
|
* The module name `artifacts-store` uses a different package (`com.correx.core.artifactstore`) without the plural `artifacts`. This is inconsistent with the module name.
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
= core-artifacts
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines the artifact model — a structured output produced during a workflow stage. Manages artifact lifecycle phases (created → validating → validated/rejected → superseded/archived) as event-sourced state, provides a kind system for typed artifact payloads (file_written, process_result), and supports materialization of file artifacts to disk.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defining the `Artifact` sealed interface and its common fields (id, session, stage, lifecycle phase, lineage, metadata)
|
||||||
|
* Managing artifact lifecycle phase transitions with state machine validation
|
||||||
|
* Tracking artifact lineage (parent IDs, relationships, validation report IDs)
|
||||||
|
* Defining and registering `ArtifactKind` implementations for typed payloads
|
||||||
|
* Providing JSON schema derivation for each artifact kind
|
||||||
|
* Materializing file artifacts to disk via `MaterializingArtifactWriter`
|
||||||
|
* Providing a repository interface for querying artifact state by session, stage, or ID
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not manage binary blob storage — that is delegated to `:core:artifacts-store` via `ArtifactStore`
|
||||||
|
* Does not validate artifact payloads — validation is handled by `:core:validation` via `ArtifactPayloadValidator`
|
||||||
|
* Does not produce artifacts — they are created by the orchestrator or infrastructure adapters
|
||||||
|
* Does not manage the full artifact content — only metadata and lifecycle state are projected
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Artifact
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Base type for all artifacts. Declares common fields.
|
||||||
|
* **fields**: `id`, `sessionId`, `stageId`, `schemaVersion`, `createdAt`, `lifecyclePhase`, `lineage`, `metadata`
|
||||||
|
|
||||||
|
=== ArtifactState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Event-sourced projection of a single artifact's lifecycle state.
|
||||||
|
* **fields**: `phase` (ArtifactLifecyclePhase), `lineage` (ArtifactLineage)
|
||||||
|
|
||||||
|
=== ArtifactReducer / DefaultArtifactReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Transforms `ArtifactState` given a stored event. Validates lifecycle phase transitions and returns `Result.Failure` for illegal transitions. Handles `ArtifactCreatedEvent`, `ArtifactValidatingEvent`, `ArtifactValidatedEvent`, `ArtifactRejectedEvent`, `ArtifactSupersededEvent`, `ArtifactArchivedEvent`, `ArtifactRelationshipAddedEvent`.
|
||||||
|
|
||||||
|
=== ArtifactProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `ArtifactReducer` as a `Projection<ArtifactState>`. Invalid lifecycle transitions are logged as warnings and do not crash replay.
|
||||||
|
|
||||||
|
=== ArtifactLineage
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Tracks an artifact's provenance.
|
||||||
|
* **fields**: `parentIds`, `relationships` (list of ArtifactRelationship), `validationReportIds`
|
||||||
|
|
||||||
|
=== ArtifactRelationship
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A directed, typed relationship between two artifacts.
|
||||||
|
* **fields**: `sourceId`, `targetId`, `type` (ArtifactRelationshipType)
|
||||||
|
|
||||||
|
=== ArtifactKind
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Declares a type of artifact with a payload serializer and JSON schema.
|
||||||
|
* **fields**: `id`, `payloadSerializer`, `llmEmitted`
|
||||||
|
|
||||||
|
=== ArtifactKindRegistry / DefaultArtifactKindRegistry
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Maps artifact kind IDs to `ArtifactKind` instances. Pre-registers `FileWrittenKind` and `ProcessResultKind`.
|
||||||
|
|
||||||
|
=== FileWrittenKind
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Artifact kind for files written to disk. Declares a JSON schema with `path`, `content`, `mode`.
|
||||||
|
|
||||||
|
=== FileWrittenPayload
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Input payload for a file write operation (before materialization).
|
||||||
|
* **fields**: `path`, `content`, `mode`
|
||||||
|
|
||||||
|
=== FileWrittenArtifact
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Canonical form of a written file after materialization (content-addressed by hash).
|
||||||
|
* **fields**: `path`, `contentHash`, `size`, `mode`
|
||||||
|
|
||||||
|
=== ProcessResultKind
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Artifact kind for process execution results. Declares a JSON schema with `command`, `exitCode`, `stdout`, `stderr`, `durationMs`.
|
||||||
|
|
||||||
|
=== ProcessResultArtifact
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Record of a process execution.
|
||||||
|
* **fields**: `command`, `exitCode`, `stdout`, `stderr`, `durationMs`
|
||||||
|
|
||||||
|
=== TypedArtifactSlot
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Associates a named artifact slot with its expected kind.
|
||||||
|
* **fields**: `name` (ArtifactId), `kind` (ArtifactKind)
|
||||||
|
|
||||||
|
=== MaterializingArtifactWriter
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Writes a `FileWrittenPayload` to disk within a sandbox root and returns a `FileWrittenArtifact`. Supports content-addressed storage by returning a hash.
|
||||||
|
|
||||||
|
=== MaterializationResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Outcome of a materialization attempt.
|
||||||
|
* **variants**: `Success(artifact, resolvedPath)`, `Failure(reason)`
|
||||||
|
|
||||||
|
=== ArtifactRepository
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Queries artifact state by session, stage, or ID.
|
||||||
|
|
||||||
|
=== JsonSchema / JsonSchemaProperty
|
||||||
|
* **kind**: data classes
|
||||||
|
* **purpose**: JSON Schema representation for artifact kind derived schemas.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `ArtifactCreatedEvent` → sets phase to `CREATED`
|
||||||
|
* `ArtifactValidatingEvent` → transitions from `CREATED` to `VALIDATING` (fails if phase is not `CREATED`)
|
||||||
|
* `ArtifactValidatedEvent` → transitions from `VALIDATING` to `VALIDATED`
|
||||||
|
* `ArtifactRejectedEvent` → transitions from `VALIDATING` to `REJECTED`
|
||||||
|
* `ArtifactSupersededEvent` → transitions from `VALIDATED` to `SUPERSEDED`
|
||||||
|
* `ArtifactArchivedEvent` → transitions from `VALIDATED` or `REJECTED` to `ARCHIVED`
|
||||||
|
* `ArtifactRelationshipAddedEvent` → adds a relationship to the lineage
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. This module is purely a projection.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, artifact lifecycle event types, `ArtifactId`, `ArtifactLifecyclePhase`, `ArtifactRelationshipType`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Lifecycle phase transitions follow a strict state machine: `CREATED → VALIDATING → {VALIDATED, REJECTED}`, then `VALIDATED → {SUPERSEDED, ARCHIVED}`, `REJECTED → ARCHIVED`
|
||||||
|
* Invalid transitions return `Result.Failure` from the reducer — they do not crash the projector
|
||||||
|
* `ArtifactProjector` catches `IllegalStateException` from invalid transitions, logs a warning, and leaves state unchanged during replay
|
||||||
|
* `ArtifactKind` registrations are idempotent — the registry is a map, so re-registering overwrites the previous entry
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-artifacts, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-artifacts.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `Artifact` sealed interface has no concrete subclasses in the current codebase. The `artifactModule` serializers module has a polymorphic block for `Artifact::class` with no subclass registrations. Concrete artifact types are needed before polymorphic serialization works.
|
||||||
|
* `ArtifactState` is a single-artifact projection — each `ArtifactState` tracks one artifact's lifecycle. The `ArtifactRepository` interface returns maps keyed by `ArtifactId`, which suggests the caller is responsible for aggregating multiple states.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* How are multiple artifact states (one per artifact) stored and queried? The repository returns maps, but the event replayer typically rebuilds a single state per session. The bridge between them is not visible in this module.
|
||||||
|
* What concrete `Artifact` subclasses are planned? (The sealed interface exists but has no implementations.)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
= core-config
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Loads and represents the Correx application configuration from a TOML file. Provides a single entry point (`ConfigLoader`) that reads `~/.config/correx/config.toml` (or a path from `CORREX_CONFIG`) and returns a `CorrexConfig` data class with validated defaults.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Read configuration file from well-known filesystem path
|
||||||
|
* Parse TOML-format configuration with minimal manual parser
|
||||||
|
* Provide type-safe config objects with sensible defaults for all subsystems
|
||||||
|
* Silently fall back to defaults when config file is missing or unparseable
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not validate configuration values semantically (beyond type coercion)
|
||||||
|
* Does not watch for file changes at runtime
|
||||||
|
* Does not manage environment variables beyond `CORREX_CONFIG`
|
||||||
|
* Does not expose configuration to remote clients
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== CorrexConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Root configuration object, aggregating all subsystem configs.
|
||||||
|
* **fields**: `server`, `tui`, `cli`, `approval`, `tools`
|
||||||
|
|
||||||
|
=== ServerConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Ktor HTTP server binding configuration.
|
||||||
|
* **fields**: `host` (default `"localhost"`), `port` (default `8080`)
|
||||||
|
|
||||||
|
=== TuiConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Terminal UI preferences.
|
||||||
|
* **fields**: `theme` (default `"dark"`), `sessionListLimit` (default `5`)
|
||||||
|
|
||||||
|
=== CliConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: CLI output formatting preferences.
|
||||||
|
* **fields**: `defaultOutput` (default `"human"`)
|
||||||
|
|
||||||
|
=== ApprovalConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Approval gate timeouts.
|
||||||
|
* **fields**: `timeoutMs` (default `300_000L`)
|
||||||
|
|
||||||
|
=== ToolsConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Tool sandbox and execution configuration.
|
||||||
|
* **fields**: `sandboxRoot`, `workingDir`, `shellAllowedExecutables`, `defaultSystemPromptPath`
|
||||||
|
|
||||||
|
=== ConfigLoader
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Stateless loader that reads and parses the TOML config file.
|
||||||
|
* **fields**: none (singleton)
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* Config is read eagerly at startup and does not participate in event sourcing.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:events` — `@Serializable` annotations on all config types
|
||||||
|
* `apps:cli`, `apps:server`, `core:approvals`, `core:tools` — consume `CorrexConfig` fields
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* ConfigLoader must never throw on missing or unparseable files — always returns `CorrexConfig()` with defaults
|
||||||
|
* All config types are `@Serializable` for potential persistence
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-config, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-config.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* TOML parser is hand-written and only supports sections with simple `key = value` pairs. Arrays, inline tables, and multi-line strings are not supported.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will config be migrated to a proper TOML library (e.g., `ktoml`)?
|
||||||
|
* Should config support hot-reload or remain load-once?
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
= core-context
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Assembles the context pack (a structured, layered representation of session state) that is fed to an LLM at inference time. Defines the layer hierarchy (L0–L4), budgeting and compression strategies for fitting context within token limits, and tracks context build lifecycle as event-sourced state.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defining the context layer hierarchy (L0 live execution, L1 stage-local, L2 compressed session memory, L3 durable project memory, L4 archival history)
|
||||||
|
* Building `ContextPack` instances from raw entries using `ContextPackBuilder`
|
||||||
|
* Compressing context entries to fit within a `TokenBudget` using strategy-specific algorithms
|
||||||
|
* Selecting entries for inference (decision point) from the context pack
|
||||||
|
* Tracking which context packs have been built for a session
|
||||||
|
* Defining steering notes — user-provided guidance attached to a session
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not produce the entries that go into the context — those are produced by other modules
|
||||||
|
* Does not interact with inference providers — the built `ContextPack` is consumed by `:core:inference`
|
||||||
|
* Does not define the content of individual layers — only the structure and assembly
|
||||||
|
* Does not manage cross-session or durable project memory directly — only models L3/L4 as layer slots
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== ContextPack
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A complete, layered context ready for inference. Includes budget tracking and compression metadata.
|
||||||
|
* **fields**: `id`, `sessionId`, `stageId`, `layers` (map of layer to entries), `budgetUsed`, `budgetLimit`, `compressionMetadata`
|
||||||
|
|
||||||
|
=== ContextLayer
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: Hierarchy level for context entries, from most immediate (L0) to most archival (L4).
|
||||||
|
* **variants**: `L0` (live execution), `L1` (stage-local), `L2` (compressed session memory), `L3` (durable project memory), `L4` (archival history)
|
||||||
|
|
||||||
|
=== ContextEntry
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A single piece of context with source tracking.
|
||||||
|
* **fields**: `id`, `layer`, `content`, `sourceType`, `sourceId`, `tokenEstimate`
|
||||||
|
|
||||||
|
=== TokenBudget
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Tracks token limit and consumption.
|
||||||
|
* **fields**: `limit`, `used`
|
||||||
|
|
||||||
|
=== CompressionMetadata
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Records what compression strategies were applied, which layers were truncated, and how many entries were dropped.
|
||||||
|
|
||||||
|
=== ContextState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Event-sourced projection of context build status.
|
||||||
|
* **fields**: `builtPackIds`, `buildingInProgress`, `interrupted`
|
||||||
|
|
||||||
|
=== ContextPackBuilder
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Builds a `ContextPack` from a list of entries within a budget.
|
||||||
|
|
||||||
|
=== DefaultContextPackBuilder
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Partitions entries into pinned (never dropped: steering notes, event history) and compressible. Applies per-source-type compression strategies and assembles the final pack.
|
||||||
|
|
||||||
|
=== DecisionPointBuilder
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Selects which layers to send to the LLM for inference.
|
||||||
|
|
||||||
|
=== DefaultDecisionPointBuilder
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Returns only L0 and L1 entries — these are the only layers suitable for direct inference.
|
||||||
|
|
||||||
|
=== ContextCompressor
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Compresses a list of entries to fit within a budget using a given strategy.
|
||||||
|
|
||||||
|
=== DefaultContextCompressor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Implements five strategies: SteeringNote (retain all), EventHistory (retain all), Artifact (keep latest per sourceId), ToolLog (deduplicate content), Conversation (keep last N entries). Applies layer-based eviction (L2 before L1).
|
||||||
|
|
||||||
|
=== CompressionStrategy
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Declares how entries of a given source type should be compressed.
|
||||||
|
* **variants**:
|
||||||
|
* `ToolLog` — deduplicate identical content, drop oldest when over budget
|
||||||
|
* `Conversation(keepLast)` — retain last N entries verbatim
|
||||||
|
* `Artifact` — keep most recent entry per sourceId
|
||||||
|
* `SteeringNote` — always retained verbatim, never dropped
|
||||||
|
* `EventHistory` — always retained, already compressed facts
|
||||||
|
|
||||||
|
=== SteeringNote
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: User-provided guidance for a session, optionally scoped to a stage.
|
||||||
|
|
||||||
|
=== ContextReducer / DefaultContextReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Transforms `ContextState` given stored events. Handles `ContextBuildingStartedEvent`, `ContextPackBuiltEvent`, `ContextBuildingFailedEvent`, `ContextBuildingInterruptedEvent`.
|
||||||
|
|
||||||
|
=== ContextProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `ContextReducer` as a `Projection<ContextState>`.
|
||||||
|
|
||||||
|
=== DefaultContextRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Rebuilds `ContextState` for a session via `EventReplayer`.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `ContextBuildingStartedEvent` → sets `buildingInProgress = true`
|
||||||
|
* `ContextPackBuiltEvent` → appends pack ID, resets building flags
|
||||||
|
* `ContextBuildingFailedEvent` → resets building flags
|
||||||
|
* `ContextBuildingInterruptedEvent` → sets `interrupted = true`, resets building in progress
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. This module is purely a projection — events are emitted by the orchestrator.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, context lifecycle event types, `ContextPackId`, `ContextEntryId`, `SessionId`, `StageId`
|
||||||
|
* `:core:artifacts` — (listed in build.gradle but not directly referenced in source files; likely for future artifact-based context entries)
|
||||||
|
* `:core:sessions` — `Projection`, `EventReplayer`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Steering notes (`sourceType == "steeringNote"`) and event history (`sourceType == "eventHistory"`) are never dropped during compression
|
||||||
|
* L0, L3, and L4 entries are never evicted by `DefaultContextCompressor` — only L2 (first), then L1
|
||||||
|
* `CompressionMetadata` is non-authoritative — it records what happened during compression but original events remain the source of truth
|
||||||
|
* `ContextPack` is built per-stage per-session; it is not persisted independently — it is disposable and rebuilt on demand
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-context, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-context.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Build.gradle lists `:core:artifacts` as a dependency, but no artifact types are directly imported in the main source. May be a forward-looking dependency or unused.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `ContextPackBuilder` and `DecisionPointBuilder` be merged? They are always used together in the context assembly flow.
|
||||||
|
* L3 (durable project memory) and L4 (archival history) are defined in the layer enum but no code populates them — are they placeholders for future epics?
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
= core-events
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Foundation layer that defines the event schema, serialization infrastructure, identity primitives, and projection framework used by every other module in the system. All state is rebuilt from events, and this module provides the tools to define, store, serialize, and replay them. It also houses shared vocabulary types (SessionId, StageId, etc.) and cross-cutting value types (Tier, ApprovalStatus, RiskLevel) that would cause circular dependencies if defined elsewhere.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defines the `EventPayload` sealed interface — all event types in the system implement it
|
||||||
|
* Provides `NewEvent` and `StoredEvent` envelopes with metadata (eventId, sessionId, timestamp, causationId, correlationId)
|
||||||
|
* Declares all concrete event types: session events, stage events, orchestration events, inference events, tool events, artifact events, approval events, context events, risk events
|
||||||
|
* Defines the `EventStore` interface for append and read operations on the event log
|
||||||
|
* Implements `EventDispatcher` as a convenience wrapper over `EventStore.append()`
|
||||||
|
* Defines shared identity types (`SessionId`, `EventId`, `StageId`, `TransitionId`, `ArtifactId`, etc.) and the underlying `TypeId` value class
|
||||||
|
* Contains the polymorphic serialization registry (`Serialization.kt`) — every `EventPayload` subclass must be registered here
|
||||||
|
* Provides `EventSerializer` / `JsonEventSerializer` for encode/decode
|
||||||
|
* Defines the projection framework (`Projection<S>`, `StateBuilder<S>`, `DefaultStateBuilder`, `EventReplayer<S>`, `DefaultEventReplayer`)
|
||||||
|
* Shares cross-module value types: `ApprovalOutcome`, `ApprovalStatus`, `GrantScope`, `Tier`, `UserSteering`, `RiskLevel`, `RiskAction`, `RiskSignal`, `RiskSummary`, `ArtifactLifecyclePhase`, `ArtifactRelationshipType`, `RetryPolicy`, `TokenUsage`, `OrchestrationState`, `OrchestrationStatus`
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not own any domain logic beyond event definition and serialization
|
||||||
|
* Does not implement the `EventStore` — that is in `infrastructure` modules (SQLite, etc.)
|
||||||
|
* Does not define reducers or projectors for domain state (those live in `core:sessions`, `core:kernel`, etc.)
|
||||||
|
* Does not enforce causal consistency or idempotency — those are the store's or kernel's responsibility
|
||||||
|
* Does not handle event routing to subscribers — `EventDispatcher` emits but does not fan out
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== EventPayload
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: base type for all domain events. Every event in the system implements this.
|
||||||
|
|
||||||
|
=== NewEvent
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: event prior to persistence, carries metadata and payload
|
||||||
|
* **fields**: metadata (EventMetadata), payload (EventPayload)
|
||||||
|
|
||||||
|
=== StoredEvent
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: event after persistence, adds sequence numbers
|
||||||
|
* **fields**: metadata, sequence (global sequence), sessionSequence (per-session monotonic), payload
|
||||||
|
|
||||||
|
=== EventMetadata
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: structured metadata attached to every event
|
||||||
|
* **fields**: eventId, sessionId, timestamp, schemaVersion, causationId, correlationId
|
||||||
|
|
||||||
|
=== EventStore
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: append events, read by session sequence, subscribe to live events, enumerate all events
|
||||||
|
|
||||||
|
=== EventDispatcher
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: convenience wrapper that builds EventMetadata and calls EventStore.append
|
||||||
|
|
||||||
|
=== TypeId
|
||||||
|
* **kind**: value class (inline)
|
||||||
|
* **purpose**: type-safe identifier wrapper around String. Used for all identity types.
|
||||||
|
* **invariants**: non-blank, no leading/trailing whitespace
|
||||||
|
|
||||||
|
=== Projection<S>
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: fold function over events: `initial()` returns base state, `apply(state, event)` returns next state
|
||||||
|
|
||||||
|
=== StateBuilder<S>
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: builds state from a list of StoredEvents
|
||||||
|
|
||||||
|
=== DefaultStateBuilder<S>
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: folds events through a Projection to produce state
|
||||||
|
|
||||||
|
=== EventReplayer<S>
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: rebuilds state for a given sessionId from the event store
|
||||||
|
|
||||||
|
=== DefaultEventReplayer<S>
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: reads events from EventStore and folds through DefaultStateBuilder
|
||||||
|
|
||||||
|
=== EventSerializer
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: serialize/deserialize EventPayload to/from String
|
||||||
|
|
||||||
|
=== JsonEventSerializer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: JSON implementation of EventSerializer using kotlinx.serialization with the polymorphic event module
|
||||||
|
|
||||||
|
=== Tier
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: five-level trust tier (T0 through T4) for tool execution and approval routing
|
||||||
|
* **variants**: T0 (level 0, most trusted), T1, T2, T3, T4 (level 4, least trusted)
|
||||||
|
|
||||||
|
=== ApprovalStatus
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **variants**: PENDING, COMPLETED, TIMED_OUT
|
||||||
|
|
||||||
|
=== ApprovalOutcome
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: APPROVED, REJECTED, AUTO_APPROVED
|
||||||
|
|
||||||
|
=== GrantScope
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **variants**: SESSION (entire session), STAGE(StageId), PROJECT(ProjectId)
|
||||||
|
|
||||||
|
=== RiskLevel
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: LOW, MEDIUM, HIGH, CRITICAL
|
||||||
|
|
||||||
|
=== RiskAction
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: PROCEED, PROMPT_USER, BLOCK
|
||||||
|
|
||||||
|
=== RiskSignal
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: discriminated signals that feed into risk assessment
|
||||||
|
* **variants**: CycleWithoutExit(cycleId), RepeatedFailure(reason, count), ValidationErrors(errorCount), InferenceTimeout(elapsedMs)
|
||||||
|
|
||||||
|
=== OrchestrationState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: projection state for workflow orchestration
|
||||||
|
* **fields**: workflowId, currentStageId, status, retryCount, pauseReason, pendingApproval, failureReason, retryPolicy
|
||||||
|
|
||||||
|
=== OrchestrationStatus
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: IDLE, RUNNING, PAUSED, COMPLETED, FAILED, CANCELED
|
||||||
|
|
||||||
|
=== RetryPolicy
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: maxAttempts and backoffMs for execution retry
|
||||||
|
|
||||||
|
=== TokenUsage
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: promptTokens + completionTokens (totalTokens computed)
|
||||||
|
|
||||||
|
=== AnyMapSerializer
|
||||||
|
* **kind**: object (KSerializer)
|
||||||
|
* **purpose**: kotlinx.serialization serializer for `Map<String, Any>` used by ToolRequest and ToolReceipt
|
||||||
|
|
||||||
|
=== Concrete event types
|
||||||
|
|
||||||
|
| Event class | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| SessionStartedEvent | Session lifecycle began |
|
||||||
|
| SessionPausedEvent | Session was paused |
|
||||||
|
| SessionResumedEvent | Session was resumed |
|
||||||
|
| SessionCompletedEvent | Session ended normally |
|
||||||
|
| SessionFailedEvent | Session ended with error |
|
||||||
|
| StageStartedEvent | Workflow stage execution began |
|
||||||
|
| StageCompletedEvent | Workflow stage completed successfully |
|
||||||
|
| StageFailedEvent | Workflow stage failed |
|
||||||
|
| TransitionExecutedEvent | Transition between stages occurred |
|
||||||
|
| WorkflowStartedEvent | Entire workflow began |
|
||||||
|
| WorkflowCompletedEvent | Entire workflow completed |
|
||||||
|
| WorkflowFailedEvent | Entire workflow failed |
|
||||||
|
| OrchestrationPausedEvent | Orchestration blocked (approval, user request) |
|
||||||
|
| OrchestrationResumedEvent | Orchestration unblocked |
|
||||||
|
| RetryAttemptedEvent | Stage execution retry occurred |
|
||||||
|
| InferenceStartedEvent | LLM inference began |
|
||||||
|
| InferenceCompletedEvent | LLM inference completed with response |
|
||||||
|
| InferenceFailedEvent | LLM inference failed |
|
||||||
|
| InferenceTimeoutEvent | LLM inference timed out |
|
||||||
|
| ModelLoadedEvent | Model loaded into memory |
|
||||||
|
| ModelUnloadedEvent | Model unloaded from memory |
|
||||||
|
| ToolInvocationRequestedEvent | Tool call was requested by LLM |
|
||||||
|
| ToolExecutionStartedEvent | Tool execution began |
|
||||||
|
| ToolExecutionCompletedEvent | Tool execution completed with receipt |
|
||||||
|
| ToolExecutionFailedEvent | Tool execution failed |
|
||||||
|
| ToolExecutionRejectedEvent | Tool execution rejected by policy/approval |
|
||||||
|
| ToolInvokedEvent | Legacy tool invocation event |
|
||||||
|
| ApprovalRequestedEvent | Approval was requested |
|
||||||
|
| ApprovalDecisionResolvedEvent | Approval decision was made |
|
||||||
|
| ApprovalGrantCreatedEvent | Approval grant was created |
|
||||||
|
| ApprovalGrantExpiredEvent | Approval grant expired |
|
||||||
|
| ContextBuildingStartedEvent | Context pack construction began |
|
||||||
|
| ContextPackBuiltEvent | Context pack was built |
|
||||||
|
| CompressionAppliedEvent | Context compression was applied |
|
||||||
|
| LayerTruncatedEvent | Context layer was truncated |
|
||||||
|
| ContextBuildingFailedEvent | Context pack construction failed |
|
||||||
|
| ContextBuildingInterruptedEvent | Context building interrupted during replay |
|
||||||
|
| ArtifactCreatedEvent | Artifact was created |
|
||||||
|
| ArtifactValidatingEvent | Artifact validation began |
|
||||||
|
| ArtifactValidatedEvent | Artifact validation passed |
|
||||||
|
| ArtifactRejectedEvent | Artifact validation failed |
|
||||||
|
| ArtifactSupersededEvent | Artifact was superseded by another |
|
||||||
|
| ArtifactArchivedEvent | Artifact was archived |
|
||||||
|
| ArtifactRelationshipAddedEvent | Relationship between artifacts was recorded |
|
||||||
|
| SteeringNoteAddedEvent | User steering note was added |
|
||||||
|
| RiskAssessedEvent | Risk assessment was performed |
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
Events enter this module via `EventDispatcher.emit()` or `EventStore.append()`. Callers construct a `NewEvent` (or provide an `EventPayload` which the dispatcher wraps) and the store persists it.
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
Events leave this module through `EventStore.read()`, `readFrom()`, `allEvents()`, or `subscribe()`/`subscribeAll()`. The serialization layer (`JsonEventSerializer`) converts `EventPayload` to/from JSON strings for storage or transport.
|
||||||
|
|
||||||
|
This module does not produce or consume events at a domain level — it is the plumbing through which all other modules' events flow.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:sessions` — uses `EventStore`, `EventReplayer`, `Projection`, `StoredEvent`, session identity types, and `DefaultStateBuilder`
|
||||||
|
* `:core:kernel` — uses `EventStore`, `EventDispatcher`, `EventMetadata`, all event types, identity types, `Projection`, `EventReplayer`
|
||||||
|
* `:core:transitions` — uses identity types (`SessionId`, `StageId`, `TransitionId`, `ArtifactId`), `StoredEvent`
|
||||||
|
* `:core:inference` — uses identity types, `TokenUsage`, `EventStore`
|
||||||
|
* `:core:approvals` — uses identity types, `ApprovalOutcome`, `ApprovalStatus`, `GrantScope`, `Tier`, `UserSteering`
|
||||||
|
* `:core:validation` — uses identity types
|
||||||
|
* `:core:tools` — uses identity types, `Tier`, `ToolReceipt`, `ToolRequest`
|
||||||
|
* `:core:context` — uses identity types (`ContextPackId`, `ContextEntryId`)
|
||||||
|
* `:core:risk` — uses `RiskLevel`, `RiskAction`, `RiskSignal`, `RiskSummary`
|
||||||
|
* `:core:artifacts` — uses identity types, `StoredEvent`
|
||||||
|
* all `:infrastructure:*` modules — implement `EventStore`, use `StoredEvent`, `NewEvent`, `EventSerializer`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Every `EventPayload` subclass must be registered in the polymorphic block in `Serialization.kt`. Missing registration = silent runtime deserialization failure.
|
||||||
|
* `TypeId` values must be non-blank with no leading/trailing whitespace.
|
||||||
|
* `StoredEvent.sequence` must be globally monotonic; `sessionSequence` must be monotonic per session.
|
||||||
|
* Event identity is by `eventId` — stores must enforce idempotency by `eventId`.
|
||||||
|
* The projection infrastructure must be deterministic: the same events fed in the same order must produce the same state.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-events, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-events.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ToolInvokedEvent` (legacy stub event) coexists with the newer `ToolInvocationRequestedEvent` / `ToolExecutionStartedEvent` / `ToolExecutionCompletedEvent` chain. Both are registered in the polymorphic block.
|
||||||
|
* `RiskSummaryId.kt` is an empty file — the type alias is defined in `IdentityTypes.kt` instead.
|
||||||
|
* `OrchestrationState` and `OrchestrationStatus` are duplicated across `core:events` and `core:kernel` as separate copies with different packages. The `core:events` copies are the canonical serialized form; the `core:kernel` copies are empty stubs.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `StoredEvent` sequence numbers be assigned by the store or generated by the caller? Current interface suggests the store assigns them.
|
||||||
|
* `Projection` and `EventReplayer` live in the `core:events` module under the `com.correx.core.sessions.projections` package — this is a naming mismatch that may cause confusion.
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
= core-inference
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Abstracts LLM inference behind a provider interface, routes requests to healthy providers based on required capabilities, tracks inference lifecycle (started → completed/failed/timed-out) as event-sourced state, and defines the data types used to communicate with inference providers (requests, responses, tool definitions, generation config).
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defining the `InferenceProvider` contract for LLM inference calls
|
||||||
|
* Routing inference requests to the most suitable healthy provider via `InferenceRouter`
|
||||||
|
* Caching provider health checks with configurable TTL
|
||||||
|
* Tracking inference records and their status transitions
|
||||||
|
* Defining shared inference types (`InferenceRequest`, `InferenceResponse`, `GenerationConfig`, `ToolDefinition`, etc.)
|
||||||
|
* Providing tokenizer abstraction for model-family-specific tokenization
|
||||||
|
* Defining model capabilities (`Coding`, `ToolCalling`, `Reasoning`, etc.) for provider selection
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not implement concrete providers — those live in `:infrastructure:*`
|
||||||
|
* Does not manage context construction — uses `ContextPack` from `:core:context`
|
||||||
|
* Does not handle tool execution — only models tool call declarations for the LLM
|
||||||
|
* Does not emit events for model loading failures — those are synchronous exceptions
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== InferenceProvider
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for a single LLM provider. Can infer, health-check, and report capabilities.
|
||||||
|
|
||||||
|
=== ProviderRegistry
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Manages the set of registered `InferenceProvider`s. Supports capability-based resolution.
|
||||||
|
|
||||||
|
=== InferenceRouter
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Resolves a provider for a given stage and set of required capabilities. Performs live health checks before selection.
|
||||||
|
|
||||||
|
=== DefaultInferenceRouter
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Uses a `RoutingStrategy` to select from healthy candidates. Caches health results with configurable TTL. Re-checks health post-selection to close TOCTOU window.
|
||||||
|
|
||||||
|
=== RoutingStrategy
|
||||||
|
* **kind**: fun interface
|
||||||
|
* **purpose**: Pure selection function — chooses a provider from candidates given required capabilities. Throws `NoEligibleProviderException` if no candidate qualifies.
|
||||||
|
|
||||||
|
=== InferenceRequest
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Complete input for an LLM inference call. Includes context pack, generation config, tool definitions, and response format.
|
||||||
|
* **fields**: `requestId`, `sessionId`, `stageId`, `contextPack`, `generationConfig`, `timeout`, `responseFormat`, `tools`
|
||||||
|
|
||||||
|
=== InferenceResponse
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Output of an LLM inference call.
|
||||||
|
* **fields**: `requestId`, `text`, `finishReason`, `tokensUsed`, `latencyMs`, `toolCalls`
|
||||||
|
|
||||||
|
=== GenerationConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Parameters for LLM generation — all fields required for deterministic replay.
|
||||||
|
* **fields**: `temperature`, `topP`, `maxTokens`, `stopSequences`, `seed`
|
||||||
|
|
||||||
|
=== FinishReason
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: Why inference finished.
|
||||||
|
* **variants**: `Stop`, `Length`, `ToolCall`, `Timeout`, `Cancelled`, `Error(message)`
|
||||||
|
|
||||||
|
=== ToolCallRequest
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A tool call proposed by the LLM.
|
||||||
|
* **fields**: `function` (ToolCallFunction with name and arguments)
|
||||||
|
|
||||||
|
=== ToolDefinition
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A tool declaration sent to the LLM as part of an inference request.
|
||||||
|
* **fields**: `function` (ToolFunction with name, description, parameters)
|
||||||
|
|
||||||
|
=== ModelCapability
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: Broad capability category for provider selection.
|
||||||
|
* **variants**: `Coding`, `ToolCalling`, `Reasoning`, `Summarization`, `General`
|
||||||
|
|
||||||
|
=== CapabilityScore
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Scores a provider on a single capability (0.0–1.0).
|
||||||
|
|
||||||
|
=== ProviderHealth
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: Health status of a provider.
|
||||||
|
* **variants**: `Healthy`, `Degraded(reason)`, `Unavailable(reason)`
|
||||||
|
|
||||||
|
=== InferenceState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Event-sourced projection of all inference records for a session.
|
||||||
|
* **fields**: `records` (list of `InferenceRecord`)
|
||||||
|
|
||||||
|
=== InferenceRecord
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Immutable record of a single inference call.
|
||||||
|
* **fields**: `requestId`, `sessionId`, `stageId`, `providerId`, `status`, `tokensUsed`, `latencyMs`, `failureReason`
|
||||||
|
|
||||||
|
=== InferenceStatus
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `STARTED`, `COMPLETED`, `FAILED`, `TIMED_OUT`
|
||||||
|
|
||||||
|
=== InferenceReducer / DefaultInferenceReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Transforms `InferenceState` given stored events. Handles `InferenceStartedEvent`, `InferenceCompletedEvent`, `InferenceFailedEvent`, `InferenceTimeoutEvent`.
|
||||||
|
|
||||||
|
=== InferenceProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `InferenceReducer` as a `Projection<InferenceState>`.
|
||||||
|
|
||||||
|
=== InferenceRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Rebuilds `InferenceState` for a session via `EventReplayer`.
|
||||||
|
|
||||||
|
=== Tokenizer
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Model-family-specific tokenization. Provider-owned — two providers for the same capability may not share a tokenizer.
|
||||||
|
|
||||||
|
=== InferenceCancellationToken
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Cancellation contract for inference. Honour cancellation cooperatively at checkpoints.
|
||||||
|
|
||||||
|
=== ResponseFormat
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Expected response format from the provider.
|
||||||
|
* **variants**: `Text`, `Json(schema)`
|
||||||
|
|
||||||
|
=== InferenceTimeout
|
||||||
|
* **kind**: value class
|
||||||
|
* **purpose**: Records a deadline duration for timeout cancellation.
|
||||||
|
|
||||||
|
=== CancellationReason
|
||||||
|
* **kind**: sealed class
|
||||||
|
* **purpose**: Why inference was cancelled.
|
||||||
|
* **variants**: `UserRequested`, `StageTimeout`, `SessionCancelled`, `ProviderEvicted`
|
||||||
|
|
||||||
|
=== ModelLoadException
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thrown when model loading fails. No event is emitted.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `InferenceStartedEvent` → adds a new record with status `STARTED`
|
||||||
|
* `InferenceCompletedEvent` → sets status to `COMPLETED`, records tokens and latency
|
||||||
|
* `InferenceFailedEvent` → sets status to `FAILED`, records failure reason
|
||||||
|
* `InferenceTimeoutEvent` → sets status to `TIMED_OUT`, records timeout as latency
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. This module is purely a projection — events are emitted by the provider or harness.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, inference lifecycle event types, `InferenceRequestId`, `ProviderId`, `SessionId`, `StageId`
|
||||||
|
* `:core:context` — `ContextPack` (included in `InferenceRequest`)
|
||||||
|
* `:core:artifacts` — `JsonSchema` (used in `ResponseFormat.Json`)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `GenerationConfig` has no implicit defaults — all relevant fields must be explicitly specified for deterministic replay
|
||||||
|
* `RoutingStrategy.select()` is a pure function with no I/O or side effects
|
||||||
|
* `DefaultInferenceRouter` never returns a provider whose health check returns `Unavailable`
|
||||||
|
* Provider IDs must be unique across the registry
|
||||||
|
* `InferenceCancellationToken` direction is one-way: a timeout can cancel the token, but cancelling the token must not retroactively signal a timeout
|
||||||
|
* `ModelLoadException` does not emit an event — model loading failure is synchronous and happens before any event would be written
|
||||||
|
* `FinishReason.Cancelled` and `InferenceStatus.TIMED_OUT` are distinct: cancellation can happen for reasons other than timeout
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-inference, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-inference.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
None.
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
= core-kernel
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
The orchestration engine that binds all core modules together into a running workflow. It owns the execution loop: resolve the next transition, execute the stage (build context, run inference, dispatch tool calls, validate), handle approvals, manage retries, and persist results as events. This is where the deterministic core meets nondeterministic inputs — the kernel is the decision-maker that translates LLM proposals and tool results into recorded state changes.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defines `SessionOrchestrator` (abstract class) — the core execution loop shared by live and replay modes
|
||||||
|
* Implements `DefaultSessionOrchestrator` (concrete) — the live execution orchestrator with real inference, validation, risk assessment, and approval gating
|
||||||
|
* Implements `ReplayOrchestrator` — environment-independent replay using recorded events, with configurable `ReplayStrategy` (Full, SkipInference, SkipValidation)
|
||||||
|
* Provides `ApprovalGateway` interface — entry point for external approval decisions (CLI, server)
|
||||||
|
* Defines `OrchestrationReducer` / `DefaultOrchestrationReducer` — reduces workflow lifecycle events to `OrchestrationState`
|
||||||
|
* Provides `OrchestrationProjector` and `OrchestrationRepository` — rebuild orchestration state from events
|
||||||
|
* Defines `OrchestratorRepositories` and `OrchestratorEngines` — dependency injection data classes wiring all required services
|
||||||
|
* Implements `RetryCoordinator` / `DefaultRetryCoordinator` — records `RetryAttemptedEvent` and applies backoff delay
|
||||||
|
* Provides `ReplayInferenceProvider` — fake inference provider for replay that reads recorded `InferenceCompletedEvent` artifacts
|
||||||
|
* Defines `WorkflowResult` sealed interface (Completed, Failed, Cancelled) and `ReplayStrategy` (Full, SkipInference, SkipValidation)
|
||||||
|
* Defines `OrchestrationConfig` — retry policy, replay strategy, stage timeout, sandbox root, default system prompt path
|
||||||
|
* Provides `ReplayArtifactMissingException` — thrown when replay cannot find a recorded inference artifact
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define workflow graphs, stage configs, or transition conditions — those are in `:core:transitions`
|
||||||
|
* Does not define event types — those are in `:core:events`
|
||||||
|
* Does not implement the event store, inference providers, or tool executors — those are in `:infrastructure`
|
||||||
|
* Does not define session lifecycle state — that is in `:core:sessions`
|
||||||
|
* Does not define approval domain models — those are in `:core:approvals`
|
||||||
|
* Does not know about specific LLM APIs, shell commands, or filesystem layout
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== SessionOrchestrator
|
||||||
|
* **kind**: abstract class
|
||||||
|
* **purpose**: base orchestrator with shared stage execution logic (context building, inference, tool dispatch, approval handling, validation)
|
||||||
|
* **key methods**: `run(sessionId, graph, config)`, `cancel(sessionId)`, `executeStage(...)`, `runInference(...)`, `resolveTransition(...)`, `emit(...)`
|
||||||
|
* **fields**: repositories (OrchestratorRepositories), engines (OrchestratorEngines), artifactStore, cancellations map, pendingApprovals map
|
||||||
|
|
||||||
|
=== DefaultSessionOrchestrator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: live orchestrator. Uses `tailrec` step loop: resolve transition → execute stage → repeat. Integrates real inference, validation pipeline, risk assessment, and approval gating.
|
||||||
|
|
||||||
|
=== ReplayOrchestrator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: deterministic replay orchestrator. Overrides `runInference` and `mapValidationOutcome` to use recorded data instead of live services. Uses `NoOpApprovalEngine` and `NoOpRiskAssessor`.
|
||||||
|
|
||||||
|
=== OrchestratorRepositories
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: DI holder for all repository dependencies: eventStore, inferenceRepository, orchestrationRepository, sessionRepository, artifactRepository, approvalRepository
|
||||||
|
|
||||||
|
=== OrchestratorEngines
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: DI holder for all engine/service dependencies: transitionResolver, contextPackBuilder, inferenceRouter, validationPipeline, approvalEngine, riskAssessor, promptResolver, toolExecutor, toolRegistry
|
||||||
|
|
||||||
|
=== ApprovalGateway
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: single method `submitApprovalDecision(requestId, decision)` — bridge between external approval UI and the orchestrator's pending approval futures
|
||||||
|
|
||||||
|
=== OrchestrationReducer / DefaultOrchestrationReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: reduces `WorkflowStartedEvent`, `WorkflowCompletedEvent`, `WorkflowFailedEvent`, `OrchestrationPausedEvent`, `OrchestrationResumedEvent`, `RetryAttemptedEvent` to `OrchestrationState`
|
||||||
|
|
||||||
|
=== OrchestrationProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: wraps `OrchestrationReducer` as a `Projection<OrchestrationState>`
|
||||||
|
|
||||||
|
=== OrchestrationRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: wraps `EventReplayer<OrchestrationState>` — `getState(sessionId)` returns the rebuilt orchestration state
|
||||||
|
|
||||||
|
=== OrchestrationConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: runtime configuration for an orchestration run
|
||||||
|
* **fields**: retryPolicy, replayStrategy, stageTimeoutMs, sandboxRoot, defaultSystemPromptPath
|
||||||
|
|
||||||
|
=== WorkflowResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: top-level result of a workflow execution
|
||||||
|
* **variants**: Completed(sessionId, terminalStageId), Failed(sessionId, reason, retryExhausted), Cancelled(sessionId)
|
||||||
|
|
||||||
|
=== ReplayStrategy
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: controls replay behavior
|
||||||
|
* **variants**: Full (re-execute everything), SkipInference (use recorded artifacts), SkipValidation (trust recorded outcomes)
|
||||||
|
|
||||||
|
=== StageOutcome
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: stage execution outcome model (success, validation failure, inference failure, approval required, cancelled)
|
||||||
|
* **variants**: Success(artifact), ValidationFailure(report, retryable), InferenceFailure(reason, retryable), ApprovalRequired(request), Cancelled
|
||||||
|
|
||||||
|
=== RetryCoordinator
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: decides whether to retry a failed stage
|
||||||
|
* **method**: `shouldRetry(sessionId, stageId, currentAttempt, policy, failureReason): Boolean`
|
||||||
|
|
||||||
|
=== DefaultRetryCoordinator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: emits `RetryAttemptedEvent`, applies backoff delay via `kotlinx.coroutines.delay`
|
||||||
|
|
||||||
|
=== ReplayInferenceProvider
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: fake `InferenceProvider` for replay — finds the first `InferenceCompletedEvent` for the given stage and returns an empty-text response with recorded token/latency data
|
||||||
|
|
||||||
|
=== ReplayArtifactMissingException
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: thrown when replay cannot find a recorded inference artifact for a given stage
|
||||||
|
|
||||||
|
=== InferenceResult (internal)
|
||||||
|
* **kind**: sealed interface (internal to SessionOrchestrator)
|
||||||
|
* **purpose**: internal inference outcome type
|
||||||
|
* **variants**: Success(response), Failed(reason), Cancelled
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound (via ApprovalGateway):*
|
||||||
|
* `ApprovalDecision` submitted externally → kernel resolves pending approval `CompletableDeferred`
|
||||||
|
|
||||||
|
*outbound (via SessionOrchestrator.emit):*
|
||||||
|
The kernel emits events for every significant action:
|
||||||
|
|
||||||
|
Session lifecycle:
|
||||||
|
* `WorkflowStartedEvent` — workflow begins
|
||||||
|
* `WorkflowCompletedEvent` — workflow ends normally
|
||||||
|
* `WorkflowFailedEvent` — workflow ends with failure or cancellation
|
||||||
|
|
||||||
|
Orchestration lifecycle:
|
||||||
|
* `OrchestrationPausedEvent` — paused for approval or user request
|
||||||
|
* `OrchestrationResumedEvent` — unblocked
|
||||||
|
|
||||||
|
Stage execution:
|
||||||
|
* `InferenceStartedEvent` — LLM inference began
|
||||||
|
* `InferenceCompletedEvent` — LLM inference completed
|
||||||
|
* `InferenceFailedEvent` — LLM inference failed
|
||||||
|
* `InferenceTimeoutEvent` — LLM inference timed out
|
||||||
|
|
||||||
|
Tool execution:
|
||||||
|
* `ToolInvocationRequestedEvent` — tool call was requested (may require approval)
|
||||||
|
* `ArtifactCreatedEvent` / `ArtifactValidatingEvent` / `ArtifactValidatedEvent` — tool-produced artifacts
|
||||||
|
|
||||||
|
Approvals:
|
||||||
|
* `ApprovalRequestedEvent` — approval requested for tool or validation
|
||||||
|
* `RiskAssessedEvent` — risk assessment result
|
||||||
|
* `ApprovalDecisionResolvedEvent` — decision recorded
|
||||||
|
* `OrchestrationResumedEvent` (after approval pass) — resumed
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `EventStore`, `EventMetadata`, `NewEvent`, `EventId`, all event types, identity types, `Projection`, `EventReplayer`
|
||||||
|
* `:core:sessions` — `DefaultSessionRepository`, `Session`
|
||||||
|
* `:core:transitions` — `TransitionResolver`, `WorkflowGraph`, `StageConfig`, `StageExecutionResult`, `EvaluationContext`, `PromptResolver`
|
||||||
|
* `:core:validation` — `ValidationPipeline`, `ValidationContext`, `ValidationOutcome`
|
||||||
|
* `:core:approvals` — `DomainApprovalRequest`, `ApprovalDecision`, `NoOpApprovalEngine`, `DefaultApprovalRepository`
|
||||||
|
* `:core:context` — `ContextPackBuilder`, `ContextPack`, `ContextEntry`, `ContextLayer`, `TokenBudget`
|
||||||
|
* `:core:inference` — `InferenceRouter`, `InferenceRequest`, `InferenceResponse`, `InferenceRepository`, `GenerationConfig`, `ResponseFormat`, `ToolCallRequest`, `ToolDefinition`, `ToolFunction`, `FinishReason`, `ProviderHealth`
|
||||||
|
* `:core:tools` — `ToolExecutor`, `ToolResult`, `ToolRegistry`
|
||||||
|
* `:core:artifacts` — `ArtifactState`, `TypedArtifactSlot`, `ArtifactRepository`
|
||||||
|
* `:core:artifacts-store` — `ArtifactStore`
|
||||||
|
* `:core:risk` — `RiskAssessor`, `RiskContext`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `DefaultSessionOrchestrator.run()` is the only path for live workflow execution — replay uses `ReplayOrchestrator.run()` instead.
|
||||||
|
* Cancellation is cooperative: the orchestrator checks `isCancelled()` at the start of each step and after inference.
|
||||||
|
* Approval decisions are synchronous: the orchestrator blocks on `CompletableDeferred.await()` until `submitApprovalDecision()` is called.
|
||||||
|
* Retry is event-driven: each retry attempt emits a `RetryAttemptedEvent` that feeds back into `OrchestrationState.retryCount`.
|
||||||
|
* The step loop is `tailrec` — no stack overflow from deep workflows.
|
||||||
|
* Replay never calls live services: `ReplayOrchestrator` replaces `approvalEngine` with `NoOpApprovalEngine`, `riskAssessor` with `NoOpRiskAssessor`, and overrides `runInference` and `mapValidationOutcome`.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-kernel, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-kernel.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `OrchestrationState.kt` and `OrchestrationStatus.kt` in this module are empty — the actual types live in `:core:events` under `com.correx.core.events.orchestration`. The kernel's `DefaultOrchestrationReducer` imports from the events module's copies.
|
||||||
|
* `StageOutcome` appears to be defined but unused within the kernel — the orchestrator uses `StageExecutionResult` (from `:core:transitions`) instead.
|
||||||
|
* `RetryPolicy.kt` in `core/kernel/execution/` is an empty file — the actual `RetryPolicy` type is in `:core:events`.
|
||||||
|
* `SessionOrchestrator` is marked abstract but has no abstract methods — `run()` and `cancel()` are abstract but the class could reasonably be concrete. The abstraction exists solely to share logic between `DefaultSessionOrchestrator` and `ReplayOrchestrator`.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* The relationship between `StageOutcome` (in kernel) and `StageExecutionResult` (in transitions) is unclear — they model the same concept with different shapes. One may be dead code.
|
||||||
|
* The `sandboxRoot` field in `OrchestrationConfig` is declared but not used anywhere in the kernel source.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
= core-observability
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module reserved for observability primitives (metrics, structured logging, tracing). Currently contains no source files — the module is a Gradle subproject with no implementation.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Intended: define observability contracts (metric types, span interfaces)
|
||||||
|
* Intended: provide base abstractions used by `infra:telemetry`
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not implement telemetry infrastructure — that belongs to `infra:telemetry`
|
||||||
|
* Does not collect runtime metrics directly
|
||||||
|
* Does not manage log output destinations
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*None.* The module has no source files.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* No events are consumed or emitted. This is a placeholder.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No integration points. The module has no dependencies beyond `core:events`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
None — module is empty.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-observability, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-observability.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
Module is a placeholder — no observability contract exists.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Which metrics framework will be used (Micrometer, OpenTelemetry, custom)?
|
||||||
|
* Will observability be event-sourced or side-channel?
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
= core-policies
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module reserved for the policy evaluation layer. Policy evaluation determines whether a proposed operation is permitted based on configured rules. Currently contains no source files — the module is a Gradle subproject with no implementation.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Intended: evaluate operational policies against proposed actions
|
||||||
|
* Intended: enforce policy denials as terminal failures for the operation path
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not perform risk assessment — that belongs to `core:risk`
|
||||||
|
* Does not gate approvals — that belongs to `core:approvals`
|
||||||
|
* Does not define workflow transitions — that belongs to `core:transitions`
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*None.* The module has no source files.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* No events are consumed or emitted. This is a placeholder.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No integration points. The module has no dependencies beyond `core:events`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
None — module is empty.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-policies, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-policies.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
Module is a placeholder — no policy evaluation logic exists.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* What policy rule format will be used (declarative, scriptable, etc.)?
|
||||||
|
* Will policies be loaded from config or from the event store?
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
= core-risk
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Assesses operational risk at decision points by evaluating validation reports, orchestration state, and inference history. Produces a `RiskSummary` that drives approval tier selection and determines whether an operation may proceed, requires user prompting, or must be blocked.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Evaluate validation errors, cycle detection, repeated failures, and inference timeouts
|
||||||
|
* Map risk signals to a risk level (LOW, MEDIUM, HIGH, CRITICAL)
|
||||||
|
* Derive a recommended action (PROCEED, PROMPT_USER, BLOCK) from the risk level
|
||||||
|
* Provide a no-op implementation for deterministic replay paths
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not enforce approvals — that belongs to `core:approvals`
|
||||||
|
* Does not define policy rules — that belongs to `core:policies`
|
||||||
|
* Does not emit events directly (signals are consumed by the approval layer)
|
||||||
|
* Does not mutate state
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== RiskAssessor
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for evaluating a `RiskContext` and producing a `RiskSummary`.
|
||||||
|
|
||||||
|
=== DefaultRiskAssessor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Production implementation that inspects validation errors, cycles, retry exhaustion, and inference timeouts from the context.
|
||||||
|
|
||||||
|
=== NoOpRiskAssessor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Returns `RiskSummary(LOW, emptyList(), PROCEED)` unconditionally. Used during deterministic replay to avoid running live risk assessment.
|
||||||
|
|
||||||
|
=== RiskContext
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Immutable snapshot of all signals available for risk assessment at a decision point.
|
||||||
|
* **fields**: `validationReport` (validation issues for the current stage), `orchestrationState` (retry count, failure reason, retry policy), `inferenceState` (inference history for timeout detection)
|
||||||
|
|
||||||
|
=== TierMapping
|
||||||
|
* **kind**: file-level extension functions
|
||||||
|
* **purpose**: Maps `RiskLevel` to `Tier` (LOW→T1, MEDIUM→T2, HIGH→T3, CRITICAL→T4) and maps individual `RiskSignal` instances to their `RiskLevel`.
|
||||||
|
* **variants**:
|
||||||
|
* `RiskLevel.toApprovalTier()` — maps to `core:approvals` Tier
|
||||||
|
* `RiskLevel.toRiskAction()` — maps to `RiskAction` (PROCEED, PROMPT_USER, BLOCK); marked `internal`
|
||||||
|
* `RiskSignal.toRiskLevel()` — per-signal severity (ValidationErrors→MEDIUM, CycleWithoutExit→MEDIUM, InferenceTimeout→MEDIUM, RepeatedFailure→HIGH); marked `internal`
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:* None directly. The `RiskContext` is assembled from:
|
||||||
|
* `ValidationReport` (produced by `core:validation`)
|
||||||
|
* `OrchestrationState` (from `core:kernel`)
|
||||||
|
* `InferenceState` (from `core:inference`)
|
||||||
|
|
||||||
|
*Outbound:* None. `RiskSummary` is returned synchronously to the caller (approval or orchestration layer).
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:events.risk` — `RiskSummary`, `RiskLevel`, `RiskSignal`, `RiskAction` (event types)
|
||||||
|
* `core:approvals` — `Tier` enum
|
||||||
|
* `core:validation.model` — `ValidationReport`, `ValidationSeverity`
|
||||||
|
* `core:inference` — `InferenceState`, `InferenceStatus`
|
||||||
|
* `core:kernel` — `OrchestrationState`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `NoOpRiskAssessor` must be used on replay paths — live assessment on replay would produce non-deterministic results
|
||||||
|
* `RiskContext.orchestrationState` is populated from config before the first assessment call, not from `StageOutcome` (which lives in `core:kernel` and cannot be imported due to dependency direction)
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-risk, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-risk.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Signal-to-level mapping is hardcoded in `TierMapping.kt` and not configurable
|
||||||
|
* No support for custom or external risk signals
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should risk signal mapping become configurable or rule-driven?
|
||||||
|
* Should risk assessment emit events for audit trail purposes?
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
= core-router
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Provides a chat/steering interface for users to interact with a running workflow. Maintains its own event-sourced state (workflow status, L2 stage memory, conversation history) and builds a tailored context pack for routing inferences that decide the next workflow action.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Accepting user input via chat or steering mode
|
||||||
|
* Maintaining conversation history per session (in-memory, scoped to facade lifecycle)
|
||||||
|
* Tracking workflow lifecycle (idle, running, paused, completed, failed) from orchestration events
|
||||||
|
* Recording L2 stage memory entries (summaries of completed/failed stages)
|
||||||
|
* Building a `ContextPack` for routing decisions that includes system prompt, workflow status, conversation turns, and stage summaries
|
||||||
|
* Routing user inputs as steering notes when in STEERING mode (emitting `SteeringNoteAddedEvent`)
|
||||||
|
* Dispatching inference requests to a provider for router responses
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not orchestrate workflows — only tracks state and provides an interface
|
||||||
|
* Does not produce L2 summaries — they are generated from event metadata
|
||||||
|
* Does not manage the full context assembly pipeline — only builds the router-specific subset
|
||||||
|
* Does not persist conversation history — it is held in-memory in the facade
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== RouterFacade
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Entry point for user input. Returns a `RouterResponse`.
|
||||||
|
|
||||||
|
=== DefaultRouterFacade
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Coordinates repository, context builder, inference router, and event store to process user input. Supports `CHAT` and `STEERING` modes.
|
||||||
|
|
||||||
|
=== RouterRepository
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Rebuilds `RouterState` for a session.
|
||||||
|
|
||||||
|
=== DefaultRouterRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Rebuilds `RouterState` via `EventReplayer`.
|
||||||
|
|
||||||
|
=== RouterContextBuilder
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Builds a router-specific `ContextPack` from `RouterState` and a token budget.
|
||||||
|
|
||||||
|
=== DefaultRouterContextBuilder
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Assembles L0 entries (system prompt, workflow status) and L1 entries (conversation history, stage summaries). Uses token budget with oldest-first eviction.
|
||||||
|
|
||||||
|
=== RouterReducer / DefaultRouterReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Transforms `RouterState` given stored events. Handles `WorkflowStartedEvent`, `WorkflowCompletedEvent`, `WorkflowFailedEvent`, `OrchestrationPausedEvent`, `OrchestrationResumedEvent`, `StageCompletedEvent`, `StageFailedEvent`.
|
||||||
|
|
||||||
|
=== RouterProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `RouterReducer` as a `Projection<RouterState>`.
|
||||||
|
|
||||||
|
=== RouterState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Event-sourced state for the router.
|
||||||
|
* **fields**: `sessionId`, `workflowStatus`, `currentStageId`, `l2Memory` (list of `RouterL2Entry`), `conversationHistory` (list of `RouterTurn`)
|
||||||
|
|
||||||
|
=== RouterConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Configuration for the router.
|
||||||
|
* **fields**: `conversationKeepLast` (default 6), `tokenBudget` (default 4096)
|
||||||
|
|
||||||
|
=== RouterResponse
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Response from the router to the user.
|
||||||
|
* **fields**: `content`, `steeringEmitted`
|
||||||
|
|
||||||
|
=== WorkflowStatus
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `IDLE`, `RUNNING`, `PAUSED`, `COMPLETED`, `FAILED`
|
||||||
|
|
||||||
|
=== StageOutcomeKind
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `SUCCESS`, `FAILURE`, `CANCELLED`
|
||||||
|
|
||||||
|
=== RouterL2Entry
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A summary of a completed/failed stage for L2 memory.
|
||||||
|
* **fields**: `stageId`, `summary`, `outcome`, `timestamp`
|
||||||
|
|
||||||
|
=== RouterTurn
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A single turn in the conversation history.
|
||||||
|
* **fields**: `role` (TurnRole), `content`, `timestamp`
|
||||||
|
|
||||||
|
=== TurnRole
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `USER`, `ROUTER`
|
||||||
|
|
||||||
|
=== ChatMode
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `CHAT`, `STEERING`
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `WorkflowStartedEvent` → sets session ID, status to `RUNNING`, current stage
|
||||||
|
* `WorkflowCompletedEvent` → sets status to `COMPLETED`, clears current stage
|
||||||
|
* `WorkflowFailedEvent` → sets status to `FAILED`, clears current stage
|
||||||
|
* `OrchestrationPausedEvent` → sets status to `PAUSED`
|
||||||
|
* `OrchestrationResumedEvent` → sets status to `RUNNING`
|
||||||
|
* `StageCompletedEvent` → appends L2 entry with outcome SUCCESS
|
||||||
|
* `StageFailedEvent` → appends L2 entry with outcome FAILURE, clears current stage
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* `SteeringNoteAddedEvent` → emitted by `DefaultRouterFacade` when mode is `STEERING` and user submits input
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, `SteeringNoteAddedEvent`, orchestration lifecycle event types, `EventStore`, `EventMetadata`, `EventId`, `SessionId`, `StageId`
|
||||||
|
* `:core:context` — `ContextPack`, `ContextEntry`, `ContextLayer`, `CompressionMetadata`, `TokenBudget`, `ContextEntryId`, `ContextPackId`
|
||||||
|
* `:core:inference` — `InferenceRouter`, `InferenceRequest`, `InferenceProvider`, `GenerationConfig`, `ModelCapability`, `ResponseFormat`
|
||||||
|
* `:core:sessions` — `Projection`, `EventReplayer`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Conversation history is held in-memory in `DefaultRouterFacade` — it is not rebuilt from events. On restart, conversation history is empty.
|
||||||
|
* `RouterTurn` is separate from the event-sourced conversation log — the reducer does not track individual turns, only L2 stage entries.
|
||||||
|
* L2 entries are created by the reducer from `StageCompletedEvent` and `StageFailedEvent` metadata, not from actual LLM-generated summaries.
|
||||||
|
* `RouterContextBuilder.L0` entries (system prompt, workflow status) are never dropped. L1 and L2 entries are evicted oldest-first under budget pressure.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-router, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-router.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Conversation history is held in a `ConcurrentHashMap` in `DefaultRouterFacade` and is not event-sourced. This means router conversation context is lost on process restart. The reducer tracks event-derived L2 entries but not individual conversation turns.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should conversation turns be event-sourced so the full conversation survives restarts?
|
||||||
|
* The `RouterTurn` timestamp is generated by `Clock.System.now()` on the facade side — should it be derived from event metadata instead?
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
= core-sessions
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines the session lifecycle model: session creation, status transitions (created → active → paused/completed/failed), and the projection infrastructure for rebuilding session state from events. Sessions are the top-level unit of work in Correx — a single workflow execution that produces artifacts and consumes LLM inference.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defines `SessionState` (status, timestamps, invalid transition count) and `SessionStatus` enum (CREATED, ACTIVE, PAUSED, COMPLETED, FAILED)
|
||||||
|
* Implements `SessionReducer` (interface) and `DefaultSessionReducer` (concrete) — translates session and stage lifecycle events into `SessionState` changes
|
||||||
|
* Provides `SessionProjector` — adapts `SessionReducer` into the `Projection<SessionState>` interface for use with the replay infrastructure
|
||||||
|
* Provides `DefaultSessionRepository` — rebuilds `Session` (sessionId + state) from events via `EventReplayer`
|
||||||
|
* Provides `SessionCounterProjection` / `SessionCounterState` — a simple projection that counts events per session
|
||||||
|
* Defines `TransitionResult` sealed interface — models whether a status transition was applied or rejected
|
||||||
|
* Defines `ApprovalMode` enum (DENY, PROMPT, AUTO, YOLO) — the approval policy mode for a session
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not produce events — all events are emitted by the kernel (`core:kernel`)
|
||||||
|
* Does not manage workflow graphs, transitions, or stage execution
|
||||||
|
* Does not interact with inference, tools, or context
|
||||||
|
* Does not enforce session lifecycle invariants — it only computes derived state from events
|
||||||
|
* Does not handle session persistence or storage
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== SessionStatus
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: lifecycle status of a session
|
||||||
|
* **variants**: CREATED, ACTIVE, PAUSED, COMPLETED, FAILED
|
||||||
|
|
||||||
|
=== SessionState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: derived projection state for a session
|
||||||
|
* **fields**: status (SessionStatus), createdAt (Instant?), updatedAt (Instant?), invalidTransitions (Int)
|
||||||
|
|
||||||
|
=== Session
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: aggregate holding a sessionId and its current derived state
|
||||||
|
|
||||||
|
=== SessionReducer
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: reduces a `StoredEvent` into a `SessionState` transition
|
||||||
|
|
||||||
|
=== DefaultSessionReducer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: concrete reducer — maps `SessionStartedEvent` → ACTIVE, `SessionPausedEvent` → PAUSED, `SessionCompletedEvent` → COMPLETED, `SessionFailedEvent`/`StageFailedEvent` → FAILED, stage progress events → ACTIVE
|
||||||
|
|
||||||
|
=== SessionProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: wraps `SessionReducer` as a `Projection<SessionState>` with initial state = CREATED
|
||||||
|
|
||||||
|
=== DefaultSessionRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: wraps `EventReplayer<SessionState>` to provide `getSession(sessionId)` and `rebuild(sessionId)` returning a `Session`
|
||||||
|
|
||||||
|
=== TransitionResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: result of attempting a session status transition
|
||||||
|
* **variants**: Applied(newState), Rejected
|
||||||
|
|
||||||
|
=== ApprovalMode
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: approval policy mode for a session
|
||||||
|
* **variants**: DENY (block all), PROMPT (ask user), AUTO (auto-approve), YOLO (skip approval entirely)
|
||||||
|
|
||||||
|
=== SessionCounterProjection / SessionCounterState
|
||||||
|
* **kind**: class / data class
|
||||||
|
* **purpose**: trivial projection counting events per session. Likely a diagnostic or testing utility.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound (consumed by reducer):*
|
||||||
|
* `SessionStartedEvent` → status becomes ACTIVE, createdAt recorded
|
||||||
|
* `SessionPausedEvent` → status becomes PAUSED
|
||||||
|
* `SessionResumedEvent` → status becomes ACTIVE
|
||||||
|
* `SessionCompletedEvent` → status becomes COMPLETED
|
||||||
|
* `SessionFailedEvent` → status becomes FAILED
|
||||||
|
* `StageStartedEvent` → status becomes ACTIVE
|
||||||
|
* `StageCompletedEvent` → status becomes ACTIVE
|
||||||
|
* `StageFailedEvent` → status becomes FAILED
|
||||||
|
* `TransitionExecutedEvent` → status becomes ACTIVE
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
This module does not emit events. All events are emitted by `core:kernel`.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, `Projection`, `EventReplayer`, `DefaultStateBuilder`, session event types (`SessionStartedEvent`, etc.), stage event types (`StageStartedEvent`, `StageCompletedEvent`, `StageFailedEvent`, `TransitionExecutedEvent`), identity types
|
||||||
|
* `:core:kernel` — uses `DefaultSessionRepository`, `Session`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `SessionState` is always derived from events via replay. It is never persisted or mutated directly.
|
||||||
|
* `createdAt` is set once from the first event's timestamp and never changes.
|
||||||
|
* `invalidTransitions` field exists in `SessionState` but is never incremented by `DefaultSessionReducer` — it is always 0.
|
||||||
|
* `SessionFailedEvent` and `StageFailedEvent` both map to FAILED status — there is no distinction in the session state between session-level and stage-level failure.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-sessions, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-sessions.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `SessionState.invalidTransitions` is declared but never written by `DefaultSessionReducer`. It is always 0. Either it is a placeholder for future use or dead code.
|
||||||
|
* `TransitionResult` is defined but unused within the module — no callers in the current codebase reference it.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* None.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
= core-stages
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module. No source files exist in this module. The build.gradle declares kotlin and serialization plugins with no dependencies. According to the project architecture, this module is intended to own stage execution logic, but the implementation has not been started.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* None — no code exists.
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* All stage execution responsibility currently lives in `:core:kernel` (SessionOrchestrator.executeStage) and `:core:transitions` (StageConfig, StageExecutor interface, StageExecutionResult).
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
n/a — no source files.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
n/a — no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
n/a — no source files.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
n/a — no source files.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
n/a — no source files.
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Empty module with no source files. The `src/` directory and `bin/` directory exist but contain nothing. Any stage-specific logic that should live here is instead implemented inline in `SessionOrchestrator.executeStage()` in `:core:kernel`, creating a ~270-line method.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* What is the intended scope of this module? Likely to host the `StageExecutor` implementations and stage-specific orchestration logic that would extract the stage execution loop from the monolithic `SessionOrchestrator` class.
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
= core-tools
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines the contract for executable tools in Correx (their name, description, parameter schema, tier, and required capabilities), tracks the lifecycle of each tool invocation (requested → started → completed/failed/rejected) as event-sourced state, and provides a registry for resolving tools by name.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defining the `Tool` interface that all tool implementations must satisfy
|
||||||
|
* Tracking tool invocation records and their status transitions
|
||||||
|
* Rebuilding tool invocation state from the event log
|
||||||
|
* Providing a registry for resolving tools by name
|
||||||
|
* Defining execution capabilities (file read, file write, network access, shell exec, process spawn)
|
||||||
|
* Validating tool requests against a tool's declared schema
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not execute tools — execution is delegated to infrastructure adapters via `ToolExecutor`
|
||||||
|
* Does not manage approval — tools declare a tier; approval is handled by `:core:approvals`
|
||||||
|
* Does not emit events — all events are consumed from the event store
|
||||||
|
* Does not define tool implementations — only contracts and state tracking
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Tool
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for an executable tool. Declares name, description, JSON parameter schema, execution tier, and required capabilities.
|
||||||
|
|
||||||
|
=== FileAffectingTool
|
||||||
|
* **kind**: interface (extends `Tool`)
|
||||||
|
* **purpose**: A tool that modifies files on disk. Adds `affectedPaths(request)` to track which files are touched.
|
||||||
|
|
||||||
|
=== ToolExecutor
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Executes a `ToolRequest` and returns a `ToolResult`. The actual execution is delegated to infrastructure adapters.
|
||||||
|
|
||||||
|
=== ToolResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Outcome of a tool execution.
|
||||||
|
* **variants**:
|
||||||
|
* `Success(invocationId, output, exitCode, metadata)` — completed successfully
|
||||||
|
* `Failure(invocationId, reason, recoverable)` — failed; `recoverable` flag signals retryability
|
||||||
|
|
||||||
|
=== ToolCapability
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: Declares what a tool can do.
|
||||||
|
* **variants**: `FILE_READ`, `FILE_WRITE`, `NETWORK_ACCESS`, `SHELL_EXEC`, `PROCESS_SPAWN`
|
||||||
|
|
||||||
|
=== ValidationResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Whether a `ToolRequest` is structurally valid against a tool's schema.
|
||||||
|
* **variants**: `Valid`, `Invalid(reason)`
|
||||||
|
|
||||||
|
=== ToolRegistry
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Resolves a tool by name and lists all registered tools.
|
||||||
|
|
||||||
|
=== ToolState
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Event-sourced projection of all tool invocations for a session.
|
||||||
|
* **fields**: `invocations` (list of `ToolInvocationRecord`)
|
||||||
|
|
||||||
|
=== ToolInvocationRecord
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Immutable record of a single tool invocation, including request, status, and receipt.
|
||||||
|
* **fields**: `invocationId`, `toolName`, `tier`, `request`, `status`, `receipt`, `requestedAt`, `completedAt`
|
||||||
|
|
||||||
|
=== ToolInvocationStatus
|
||||||
|
* **kind**: enum
|
||||||
|
* **purpose**: Lifecycle status of a tool invocation.
|
||||||
|
* **variants**: `REQUESTED`, `STARTED`, `COMPLETED`, `FAILED`, `REJECTED`
|
||||||
|
|
||||||
|
=== ToolReducer / DefaultToolReducer
|
||||||
|
* **kind**: interface / class
|
||||||
|
* **purpose**: Transforms `ToolState` given a stored event. Handles `ToolInvocationRequestedEvent`, `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`, `ToolExecutionFailedEvent`, `ToolExecutionRejectedEvent`.
|
||||||
|
|
||||||
|
=== ToolProjector
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps `ToolReducer` as a `Projection<ToolState>`.
|
||||||
|
|
||||||
|
=== DefaultToolRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Rebuilds `ToolState` for a session via `EventReplayer`.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* `ToolInvocationRequestedEvent` → adds a new record with status `REQUESTED`
|
||||||
|
* `ToolExecutionStartedEvent` → sets status to `STARTED`
|
||||||
|
* `ToolExecutionCompletedEvent` → sets status to `COMPLETED`, attaches receipt
|
||||||
|
* `ToolExecutionFailedEvent` → sets status to `FAILED`
|
||||||
|
* `ToolExecutionRejectedEvent` → sets status to `REJECTED`
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. This module is purely a projection — events are emitted by the orchestrator.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `StoredEvent`, `ToolRequest`, `ToolReceipt`, invocation lifecycle event types, `ToolInvocationId`
|
||||||
|
* `:core:approvals` — `Tier` (tools declare their execution tier)
|
||||||
|
* `:core:sessions` — `Projection`, `EventReplayer`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Status transitions are append-only: records are never removed from `ToolState`
|
||||||
|
* A record transitions through statuses monotonically: `REQUESTED → STARTED → {COMPLETED, FAILED, REJECTED}`
|
||||||
|
* `ToolInvocationRecord.completedAt` is always null until the invocation reaches a terminal status
|
||||||
|
* Each `ToolInvocationId` must be unique across all sessions
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-tools, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-tools.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
None.
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
= core-transitions
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines the workflow graph model and transition resolution machinery. A workflow is a directed graph of stages with conditional edges; this module provides the types to construct, validate, and navigate that graph. It also handles cycle detection, policy binding for cycles, and mapping stage execution results to events. This module is the "navigation system" — it decides where to go next, not how to execute the work.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Defines `WorkflowGraph` — a directed graph of `StageConfig` nodes connected by `TransitionEdge` edges
|
||||||
|
* Defines `StageConfig` — configuration for a single stage: required model capabilities, token budget, generation config, allowed tools, produced artifact slots, needed dependencies
|
||||||
|
* Defines `TransitionCondition` — a functional interface that evaluates whether a transition should fire, given an `EvaluationContext`
|
||||||
|
* Provides built-in conditions: `AlwaysTrue`, `VariableEquals`, `ArtifactPresent`, `ArtifactAbsent`, `ArtifactValidated`, `AllOf`, `AnyOf`, `Not`
|
||||||
|
* Implements `TransitionResolver` / `DefaultTransitionResolver` — evaluates outgoing edges from the current stage and returns a `TransitionDecision` (Move, Stay, Blocked, NoMatch)
|
||||||
|
* Defines `StageExecutor` interface and `StageExecutionRequest`/`StageExecutionResult` types — abstraction for executing a single stage
|
||||||
|
* Defines `EvaluationContext` — carries sessionId, currentStageId, artifact states, and variables for condition evaluation
|
||||||
|
* Implements deterministic cycle detection: `CycleExtractor`, `DeterministicAdjacencyBuilder`, `CycleDfs`, `CycleCanonicalizer`
|
||||||
|
* Defines cycle policy types: `CyclePolicy` (Retry, Refinement, Approval), `CyclePolicyBinding`, `CycleSignature`, `CycleSignatureFactory`
|
||||||
|
* Provides `CyclePolicyResolver` — looks up policy bindings for detected cycles
|
||||||
|
* Provides `PolicyValidation` — validates that all known cycles have policy bindings
|
||||||
|
* Provides `StageExecutionEventMapper` / `DefaultStageExecutionEventMapper` — converts stage execution results into `TransitionExecutedEvent`, `StageCompletedEvent`, and `StageFailedEvent`
|
||||||
|
* Provides `PromptResolver` — a functional interface for loading prompt templates by path
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not execute stages — stage execution is delegated to `StageExecutor` implementations (in `core:kernel` or infrastructure)
|
||||||
|
* Does not interact with the event store directly — it defines types used by the kernel
|
||||||
|
* Does not own session lifecycle or orchestration loop
|
||||||
|
* Does not define the inference, context, or tool execution infrastructure
|
||||||
|
* Does not enforce cycle policies at runtime — policy enforcement is the kernel's responsibility
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== WorkflowGraph
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: a complete workflow definition as a directed graph of stages
|
||||||
|
* **fields**: id, stages (Map<StageId, StageConfig>), transitions (Set<TransitionEdge>), start (StageId)
|
||||||
|
* **invariants**: id is non-blank; start stage must exist in stages
|
||||||
|
|
||||||
|
=== StageConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: configuration for a single workflow stage
|
||||||
|
* **fields**: requiredCapabilities (Set<ModelCapability>), tokenBudget, generationConfig (temperature, topP, maxTokens), allowedTools, maxRetries, produces (List<TypedArtifactSlot>), needs (Set<ArtifactId>), metadata (Map<String, String>)
|
||||||
|
|
||||||
|
=== TransitionEdge
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: a directed edge between two stages with a condition
|
||||||
|
* **fields**: id (TransitionId), from (StageId), to (StageId), condition (TransitionCondition)
|
||||||
|
|
||||||
|
=== TransitionCondition
|
||||||
|
* **kind**: fun interface
|
||||||
|
* **purpose**: evaluates to true/false given an `EvaluationContext`
|
||||||
|
* **method**: `evaluate(context: EvaluationContext): Boolean`
|
||||||
|
|
||||||
|
=== TransitionResolver
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: given a WorkflowGraph and EvaluationContext, produces a TransitionDecision
|
||||||
|
|
||||||
|
=== DefaultTransitionResolver
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: iterates outgoing edges from the current stage in deterministic order, evaluates each condition, returns the first matching Move or Stay/NoMatch
|
||||||
|
|
||||||
|
=== TransitionDecision
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: the result of resolving which transition to take
|
||||||
|
* **variants**:
|
||||||
|
* `Move(transitionId, to)` — follow this edge to the target stage
|
||||||
|
* `Stay` — no condition matched, remain at current stage
|
||||||
|
* `Blocked(reason)` — a condition explicitly blocked the transition
|
||||||
|
* `NoMatch` — no outgoing edges exist from the current stage
|
||||||
|
|
||||||
|
=== EvaluationContext
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: inputs for condition evaluation
|
||||||
|
* **fields**: sessionId, currentStage (StageId), artifacts (Map<ArtifactId, ArtifactState>), variables (Map<String, String>)
|
||||||
|
|
||||||
|
=== StageExecutor
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: executes a single stage and returns success or failure
|
||||||
|
|
||||||
|
=== StageExecutionRequest
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: parameters for executing a stage
|
||||||
|
* **fields**: sessionId, from (StageId), to (StageId), transitionId, context (EvaluationContext)
|
||||||
|
|
||||||
|
=== StageExecutionResult
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: outcome of stage execution
|
||||||
|
* **variants**: Success(producedArtifacts), Failure(reason, retryable)
|
||||||
|
|
||||||
|
=== TransitionConditionEvaluator
|
||||||
|
* **kind**: fun interface
|
||||||
|
* **purpose**: evaluates a TransitionCondition against an EvaluationContext
|
||||||
|
|
||||||
|
=== StageExecutionEventMapper
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: converts StageExecutionRequest + StageExecutionResult into a list of EventPayloads
|
||||||
|
|
||||||
|
=== DefaultStageExecutionEventMapper
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: produces TransitionExecutedEvent + StageCompletedEvent (on success) or TransitionExecutedEvent + StageFailedEvent (on failure)
|
||||||
|
|
||||||
|
=== CyclePolicy
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: what to do when a cycle is detected
|
||||||
|
* **variants**: Retry(maxAttempts), Refinement(maxIterations), Approval(timeoutMs)
|
||||||
|
|
||||||
|
=== CycleSignature
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: canonical identity for a cycle — sorted set of nodes and edges
|
||||||
|
* **fields**: nodes (SortedSet<StageId>), edges (SortedSet<Pair<StageId, StageId>>)
|
||||||
|
|
||||||
|
=== CyclePolicyBinding
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: binds a CycleSignature to a CyclePolicy
|
||||||
|
|
||||||
|
=== CyclePolicyResolver
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: looks up the policy for a given cycle signature from a set of bindings
|
||||||
|
|
||||||
|
=== PolicyValidation
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: validates that all known cycles have policy bindings, returns list of errors
|
||||||
|
|
||||||
|
=== CycleExtractor
|
||||||
|
* **kind**: class (internal)
|
||||||
|
* **purpose**: runs DFS on the workflow graph to detect cycles
|
||||||
|
|
||||||
|
=== CycleCanonicalizer
|
||||||
|
* **kind**: object (internal)
|
||||||
|
* **purpose**: normalizes detected cycles by rotating to the minimum node and deduplicating
|
||||||
|
|
||||||
|
=== PromptResolver
|
||||||
|
* **kind**: fun interface
|
||||||
|
* **purpose**: resolves a prompt path string to its content
|
||||||
|
|
||||||
|
=== Built-in conditions
|
||||||
|
|
||||||
|
| Type | evaluates to true when |
|
||||||
|
|---|---|
|
||||||
|
| `AlwaysTrue` | unconditional |
|
||||||
|
| `VariableEquals(key, value)` | `context.variables[key] == value` |
|
||||||
|
| `ArtifactPresent(artifactId)` | artifact exists in `context.artifacts` |
|
||||||
|
| `ArtifactAbsent(artifactId)` | artifact absent from `context.artifacts` |
|
||||||
|
| `ArtifactValidated(artifactId)` | artifact phase == VALIDATED |
|
||||||
|
| `AllOf(conditions)` | all sub-conditions evaluate to true |
|
||||||
|
| `AnyOf(conditions)` | any sub-condition evaluates to true |
|
||||||
|
| `Not(condition)` | sub-condition evaluates to false |
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
This module does not consume events directly. The kernel provides event data to the resolver via `EvaluationContext`.
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
This module does not emit events. It defines `StageExecutionEventMapper` which the kernel uses to produce:
|
||||||
|
* `TransitionExecutedEvent` — always emitted after a transition resolves
|
||||||
|
* `StageCompletedEvent` — on execution success
|
||||||
|
* `StageFailedEvent` — on execution failure
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — identity types (`SessionId`, `StageId`, `TransitionId`, `ArtifactId`), event types (`TransitionExecutedEvent`, `StageCompletedEvent`, `StageFailedEvent`), `SessionId`
|
||||||
|
* `:core:inference` — `GenerationConfig`, `ModelCapability` (used in `StageConfig`)
|
||||||
|
* `:core:artifacts` — `TypedArtifactSlot` (used in `StageConfig.produces`), `ArtifactState` (used in `EvaluationContext`)
|
||||||
|
* `:core:kernel` — implements `StageExecutor`, uses `TransitionResolver`, `WorkflowGraph`, `StageExecutionResult`, `EvaluationContext`, `PromptResolver`, `StageExecutionEventMapper`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `WorkflowGraph.start` must be a key in `WorkflowGraph.stages` (enforced by require).
|
||||||
|
* `WorkflowGraph.id` must be non-blank.
|
||||||
|
* Transition edges from a given stage are evaluated in a deterministic order (sorted by from, id, to).
|
||||||
|
* Cycle detection is deterministic: DFS traversal order is fixed by sorted adjacency list.
|
||||||
|
* Detected cycles are canonicalized: rotated to the minimum node and deduplicated.
|
||||||
|
* `StageConfig.maxRetries` is a configuration value only — actual retry logic is in the kernel.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-transitions, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-transitions.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `StageConfig` imports `TypedArtifactSlot` from `:core:artifacts` and `GenerationConfig`/`ModelCapability` from `:core:inference`, creating cross-core dependencies. The build.gradle explicitly declares these as project dependencies, violating the "no cross-core" convention noted in CLAUDE.md.
|
||||||
|
* `GraphOrdering.sortedStages()` and `sortedTransitions()` are marked `internal` but appear unused within the module — the ordering logic is duplicated in `DeterministicAdjacencyBuilder` and `TransitionOrdering`.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should cycle policies be enforced at the transitions level (before execution) or at the orchestration level (during execution)? Currently, the module provides the policy model but enforcement is in the kernel's retry coordinator.
|
||||||
|
* The relationship between `StageConfig.maxRetries` (in this module) and `RetryPolicy.maxAttempts` (in `:core:events`) is unclear — they may be redundant.
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
= core-validation
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Runs a pipeline of validators against a `ValidationContext` (containing the workflow graph, detected cycles, cycle policies, and session state) to produce a `ValidationReport`. If structural errors are found, the pipeline rejects immediately as non-retryable. If issues warrant human review, it signals `NeedsApproval`.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Validating workflow graph structure (start node exists, no dangling transitions)
|
||||||
|
* Validating transition integrity (endpoint references, deterministic ordering)
|
||||||
|
* Validating session temporal and status consistency
|
||||||
|
* Running semantic rules (e.g. cycle policy binding checks)
|
||||||
|
* Validating artifact payloads against their declared schemas
|
||||||
|
* Producing a `ValidationOutcome` (Passed, Rejected, or NeedsApproval)
|
||||||
|
* Determining whether validation failures are retryable
|
||||||
|
* Triggering approval requests when validation risk exceeds thresholds
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not enforce approvals or block execution — only signals the need for approval
|
||||||
|
* Does not modify state or emit events — it is a pure validation function
|
||||||
|
* Does not manage the lifecycle of validation reports
|
||||||
|
* Does not validate LLM outputs directly — validates artifacts and graph structures
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Validator
|
||||||
|
* **kind**: fun interface
|
||||||
|
* **purpose**: Performs a single validation pass and returns a `ValidationSection`.
|
||||||
|
|
||||||
|
=== ValidationPipeline
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Runs a list of validators in sequence. Short-circuits on structural errors (non-retryable rejection). Optionally evaluates an `ApprovalTrigger` after all validators pass.
|
||||||
|
* **fields**: `validators` (list), `approvalTrigger` (nullable)
|
||||||
|
|
||||||
|
=== ValidationOutcome
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Result of a validation run.
|
||||||
|
* **variants**:
|
||||||
|
* `Passed(report)` — validation succeeded
|
||||||
|
* `Rejected(report, retryable)` — validation failed; `retryable` is set by the validator and must not be overridden
|
||||||
|
* `NeedsApproval(request)` — validation passed but human approval is required
|
||||||
|
|
||||||
|
=== ValidationReport
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Collection of `ValidationSection`s produced by a pipeline run.
|
||||||
|
|
||||||
|
=== ValidationSection
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Named group of issues from a single validator.
|
||||||
|
* **fields**: `name`, `issues`, `metadata`
|
||||||
|
|
||||||
|
=== ValidationIssue
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: A single finding with machine-readable code, human message, severity, and optional location.
|
||||||
|
|
||||||
|
=== ValidationSeverity
|
||||||
|
* **kind**: enum
|
||||||
|
* **variants**: `INFO`, `WARNING`, `ERROR`
|
||||||
|
|
||||||
|
=== ValidationLocation
|
||||||
|
* **kind**: sealed interface
|
||||||
|
* **purpose**: Points to where an issue was found.
|
||||||
|
* **variants**:
|
||||||
|
* `Graph(stageId, transitionId)` — issue in workflow graph
|
||||||
|
* `Session(sessionId)` — issue in session state
|
||||||
|
|
||||||
|
=== ValidationContext
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Input to validators, containing the workflow graph, cycles, policies, and session state.
|
||||||
|
* **fields**: `graph` (WorkflowGraph), `detectedCycles`, `cyclePolicies`, `sessionState`
|
||||||
|
|
||||||
|
=== GraphValidator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Checks start node existence, dangling transitions, and records detected cycles as INFO issues.
|
||||||
|
|
||||||
|
=== TransitionValidator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Checks transition endpoint integrity and deterministic ordering per source node.
|
||||||
|
|
||||||
|
=== SessionValidator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Checks temporal consistency, negative invalid transition count, and suspicious failure states.
|
||||||
|
|
||||||
|
=== SemanticValidator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Runs a list of `SemanticRule` instances.
|
||||||
|
|
||||||
|
=== SemanticRule
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Validates a single semantic property.
|
||||||
|
|
||||||
|
=== CyclePolicyBindingRule
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Warns when detected cycles lack a policy binding. Only active when `requirePolicyForCycles` is true.
|
||||||
|
|
||||||
|
=== ArtifactPayloadValidator
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Validates artifact payloads (`FileWrittenArtifact`, `ProcessResultArtifact`) against their schemas by reading from `ArtifactStore`.
|
||||||
|
|
||||||
|
=== ApprovalTrigger
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Evaluates a `ValidationReport` and returns an `ApprovalRequest` if error count > 0 or cycle policies are missing.
|
||||||
|
|
||||||
|
=== ApprovalRequest (validation)
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Signals that an operation requires human approval.
|
||||||
|
* **fields**: `sessionId` (SessionId?, nullable — may be absent at early validation stage), `riskSummary` (ValidationRiskStats), `validationReport`
|
||||||
|
|
||||||
|
=== ValidationRiskStats
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Summary of error and warning counts plus cycle policy status.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:*
|
||||||
|
* None. Validation is invoked by the orchestrator before executing operations. It does not subscribe to events.
|
||||||
|
|
||||||
|
*outbound:*
|
||||||
|
* None. Validation is a pure function — it returns a sealed outcome, it does not emit events.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — shared identity types (SessionId, StageId, etc.)
|
||||||
|
* `:core:sessions` — `SessionState`, `SessionStatus`
|
||||||
|
* `:core:transitions` — `WorkflowGraph`, `DetectedCycle`, `CyclePolicyBinding`, `TransitionEdge`, `TransitionOrdering`, `CycleSignatureFactory`
|
||||||
|
* `:core:artifacts` — `FileWrittenArtifact`, `ProcessResultArtifact`, `TypedArtifactSlot`
|
||||||
|
* `:core:artifacts-store` — `ArtifactStore`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ValidationOutcome.Rejected.retryable` is set by the validator and must not be overridden by the orchestrator
|
||||||
|
* Structural validation errors (ERROR severity in the graph) produce `retryable = false`
|
||||||
|
* Pipeline short-circuits on the first ERROR severity issue — remaining validators are skipped
|
||||||
|
* `ApprovalRequest` produced by `ApprovalTrigger` is implicitly `Tier.T2 (REVERSIBLE)` — validation affects workflow progression, not external state
|
||||||
|
* The `RiskSummary` and `ValidationRiskStats` types are duplicate definitions of the same data
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, core-validation, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/core-validation.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `RiskSummary` and `ValidationRiskStats` are separate data classes with identical fields — likely a leftover from refactoring.
|
||||||
|
* `ApprovalRequest` lives in the validation module but is consumed by the approvals module, which has its own `DomainApprovalRequest` type. These are two different representations of the same concept with no shared type.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should the `ApprovalRequest` produced by validation share a common type with the approvals module instead of being a validation-local class?
|
||||||
|
* Should validation produce structured IDs for reports and risk summaries that are meaningful to the approvals module?
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
= infra-artifacts-cas
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Content-addressable storage (CAS) for binary artifact blobs, backed by append-only segment files on disk. Provides write-once, read-by-hash semantics with integrity verification (CRC32C + BLAKE3), automatic segment rotation, tail recovery after crash, compaction of dead data, and eviction of unreferenced segments. The index is maintained in a SQLite database.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Store binary blobs addressed by BLAKE3 hash (content-addressable)
|
||||||
|
* Append records to immutable segment files with header (length, CRC32C, BLAKE3 hash)
|
||||||
|
* Look up and read records by hash via a SQLite index
|
||||||
|
* Detect and recover partially-written tail records after process crash
|
||||||
|
* Compact fragmented segments: defragment live records into new segments, delete old ones
|
||||||
|
* Evict dead segments (zero live bytes) when total storage exceeds `maxTotalBytes`
|
||||||
|
* Materialize `FileWrittenPayload` to the filesystem for artifact writing (sandbox-aware)
|
||||||
|
* Coordinate flush ordering with the event store to maintain consistency
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not manage artifact lifecycle (creation, validation, archival) — that belongs to `core:artifacts`
|
||||||
|
* Does not store event data — that belongs to `infra:persistence`
|
||||||
|
* Does not enforce access control — that belongs to `infra:security`
|
||||||
|
* Does not provide a queryable artifact index beyond hash lookup
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== CasArtifactStore
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Main entry point implementing `ArtifactStore`. Manages a `SegmentWriter` and `SegmentReader`, coordinates compaction and eviction with mutexes, and provides crash recovery via `TailScanner`.
|
||||||
|
* **fields**: `config`, `index`, `segmentsDir`, `writer`, `reader`
|
||||||
|
|
||||||
|
=== CasConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Configuration for the CAS store.
|
||||||
|
* **fields**: `rootDir` (filesystem root), `maxSegmentBytes` (default 64MB), `maxTotalBytes` (default `Long.MAX_VALUE`)
|
||||||
|
|
||||||
|
=== ArtifactIndex (interface)
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for the hash-to-location index. Supports lookup, insert, update, and segment-scoped iteration/deletion.
|
||||||
|
|
||||||
|
=== SqliteArtifactIndex
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: SQLite-backed implementation of `ArtifactIndex`. Uses WAL mode and `INSERT OR IGNORE` / `ON CONFLICT DO UPDATE` semantics.
|
||||||
|
|
||||||
|
=== SegmentLayout
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Binary layout constants and encoding/decoding utilities. Record header = 4 bytes payload length + 4 bytes CRC32C + 32 bytes BLAKE3 hash.
|
||||||
|
* **constants**: `HASH_SIZE = 32`, `HEADER_SIZE = 40`
|
||||||
|
|
||||||
|
=== SegmentWriter
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Writes records to an append-only segment file. Rotates to a new file when the current segment exceeds `maxSegmentBytes`. Supports `fsync()`.
|
||||||
|
|
||||||
|
=== SegmentReader
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Reads a record from a segment file by `Location`, verifying CRC32C and BLAKE3 hash on read. Throws `CorruptRecordException` on mismatch.
|
||||||
|
|
||||||
|
=== Location
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Pointer to a record within a segment file.
|
||||||
|
* **fields**: `segmentId`, `offset`, `length`
|
||||||
|
|
||||||
|
=== TailScanner
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: After a crash, scans the active segment from the last known tail offset, recovers intact records, and truncates the file at the first corrupted byte.
|
||||||
|
* **fields**: `segmentsDir`, `index`
|
||||||
|
|
||||||
|
=== LivenessScanner
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Scans the event store for all referenced artifact IDs (`InferenceStartedEvent.promptArtifactId`, `InferenceCompletedEvent.responseArtifactId`) and computes per-segment live-byte statistics.
|
||||||
|
|
||||||
|
=== Compactor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Selects segments below a live-ratio threshold (default 0.5), reads live entries, writes them to a new segment, atomically swaps index entries, and deletes old segment files.
|
||||||
|
* **fields**: `config`, `index`, `liveness`, `storeMutex`
|
||||||
|
|
||||||
|
=== Evictor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: When total storage exceeds `maxTotalBytes`, deletes segments that have zero live bytes (not the active segment).
|
||||||
|
* **fields**: `config`, `index`, `liveness`
|
||||||
|
|
||||||
|
=== DefaultMaterializingArtifactWriter
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Implements `MaterializingArtifactWriter` by writing `FileWrittenPayload` to the filesystem under a sandbox root, computing the BLAKE3 hash, and returning a `FileWrittenArtifact`. Prevents path traversal.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:*
|
||||||
|
* `InferenceStartedEvent` / `InferenceCompletedEvent` — consumed by `LivenessScanner` to determine which artifacts are live (referenced by the event log)
|
||||||
|
|
||||||
|
*Outbound:* None. The CAS store does not emit events.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:artifactstore` — `ArtifactStore` (interface implemented by `CasArtifactStore`)
|
||||||
|
* `core:artifacts` — `MaterializingArtifactWriter`, `MaterializationResult`, `FileWrittenArtifact`, `FileWrittenPayload`
|
||||||
|
* `core:events` — `StoredEvent`, `InferenceStartedEvent`, `InferenceCompletedEvent`, `ArtifactId`
|
||||||
|
* `infra:persistence` — `EventStore` (consumed by `LivenessScanner`)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Records are immutable once written — updates are not possible (new hash = new record)
|
||||||
|
* Each record is verified on read: CRC32C and BLAKE3 must match the header
|
||||||
|
* The `Compactor` holds `storeMutex` during the atomic swap to prevent concurrent puts from observing stale locations
|
||||||
|
* Compaction and eviction are mutually exclusive (`maintenanceMutex`)
|
||||||
|
* `TailScanner` truncates the active segment at the first corrupt record before the writer resumes
|
||||||
|
* The `flushBefore` protocol coordinates event store and CAS flushing to maintain write ordering
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-artifacts-cas, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-artifacts-cas.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `LivenessScanner.collectLiveIds()` iterates all events in the store — O(n) per compaction/eviction cycle, may be slow for large event logs
|
||||||
|
* `Evictor` only evicts segments with zero live bytes — partially-dead segments are left for compaction
|
||||||
|
* `DefaultMaterializingArtifactWriter.materialize()` computes BLAKE3 from a fresh byte array read after writing — could compute during write instead
|
||||||
|
* No garbage collection for the SQLite index — deleted segment entries leave behind index rows that are only removed via `deleteEntriesIn`
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should liveness scanning support incremental updates via event subscription rather than full replay?
|
||||||
|
* Should segment files be encrypted at rest?
|
||||||
|
* Should there be a maximum segment count to bound file descriptor usage?
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
= infra-inference-commons
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Defines shared abstractions and types for inference provider lifecycle management. Provides the `ModelManager` interface for loading/unloading models, the `ManagedInferenceProvider` wrapper that ties an `InferenceProvider` to a model lifecycle, and supporting types (`ModelDescriptor`, `ResidencyMode`, `ModelLoadException`).
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Define the contract for model lifecycle management (load, unload, health check)
|
||||||
|
* Provide a managed wrapper around `InferenceProvider` that supports model replacement
|
||||||
|
* Define residency modes that govern when models are unloaded (persistent, dynamic, ephemeral)
|
||||||
|
* Describe model configuration via `ModelDescriptor`
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not implement any inference provider — that belongs to `infra:inference:llama-cpp` and similar
|
||||||
|
* Does not handle provider routing or selection
|
||||||
|
* Does not define the core inference contract — that belongs to `core:inference`
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== ModelManager
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for loading, unloading, and health-checking inference models. Enforces single-model-at-a-time.
|
||||||
|
* **methods**: `load(descriptor)`, `unload(modelId)`, `currentModel()`, `healthCheck()`
|
||||||
|
|
||||||
|
=== ManagedInferenceProvider
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Extends `InferenceProvider` with lifecycle management (unload, isLoaded, load a different model).
|
||||||
|
* **methods**: `unload()`, `isLoaded()`, `load(newDescriptor)`
|
||||||
|
|
||||||
|
=== ModelDescriptor
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Describes a model instance with its path, residency mode, context size, and capabilities.
|
||||||
|
* **fields**: `modelId`, `modelPath`, `residencyMode`, `idleTimeoutMs` (default 60000), `contextSize` (default 8192), `capabilities`
|
||||||
|
|
||||||
|
=== ResidencyMode
|
||||||
|
* **kind**: enum class
|
||||||
|
* **purpose**: Governs model unloading behavior.
|
||||||
|
* **variants**: `PERSISTENT` (never unload), `DYNAMIC` (unload after idle timeout), `EPHEMERAL` (unload immediately after inference)
|
||||||
|
|
||||||
|
=== ModelLoadException
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Exception thrown when model loading or unloading fails. Carries `modelId` and optional cause.
|
||||||
|
* **fields**: `message`, `modelId`, `cause`
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* The commons layer defines abstractions only; events are emitted by implementations.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:inference` — `InferenceProvider`, `ProviderHealth`, `CapabilityScore`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `ModelManager` enforces single-model-at-a-time via mutex in implementations
|
||||||
|
* Model loading must succeed through health checks before the provider is returned
|
||||||
|
* `ManagedInferenceProvider.load()` replaces the current model entirely
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-inference-commons, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-inference-commons.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ManagedInferenceProvider.load()` uses `as? ManagedInferenceProvider` safe cast — returns null then throws explicit `ModelLoadException`, not `ClassCastException`
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `ModelManager` support multiple concurrent models for different sessions?
|
||||||
|
* Should `ResidencyMode.DYNAMIC` have a configurable timeout per-model or per-manager?
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
= infra-inference-llama-cpp
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Implements the inference provider contracts for llama.cpp. Manages the lifecycle of a llama.cpp server subprocess, communicates with it via HTTP (Ktor client), and provides tokenization, grammar-constrained generation, and model swapping. Converts `JsonSchema` definitions to GBNF grammar for structured output.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Start, health-check, and stop a `llama-server` subprocess per model
|
||||||
|
* Expose an `InferenceProvider` that sends chat completion requests via the OpenAI-compatible HTTP API
|
||||||
|
* Provide tokenization by delegating to the llama.cpp `/tokenize` endpoint
|
||||||
|
* Convert JSON Schema to GBNF grammar strings for structured output constraints
|
||||||
|
* Emit `ModelLoadedEvent` / `ModelUnloadedEvent` on lifecycle transitions
|
||||||
|
* Enforce single-model-at-a-time via mutex
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define inference contracts — that belongs to `core:inference` and `infra:inference:commons`
|
||||||
|
* Does not manage GPU residency or scheduling
|
||||||
|
* Does not implement provider routing or fallback
|
||||||
|
* Does not cache inference results
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== LlamaCppInferenceProvider
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: HTTP client-based `InferenceProvider` that sends chat completion requests to a llama.cpp server. Builds messages from `ContextPack`, applies GBNF grammar for JSON responses, and returns `InferenceResponse`.
|
||||||
|
* **fields**: `descriptor` (model config), `baseUrl` (default `http://127.0.0.1:10000`), `httpClient` (Ktor CIO)
|
||||||
|
|
||||||
|
=== DefaultModelManager
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: `ModelManager` implementation that spawns and manages a `llama-server` subprocess. Thread-safe via `Mutex`. Emits lifecycle events to the event store. Supports health-check polling and process restart for model swaps.
|
||||||
|
* **fields**: `llamaServerBin` (default `"llama-server"`), `host`, `port`, `healthTimeoutMs` (default 30000), `eventStore`, `httpClient`, `eventDispatcher`
|
||||||
|
|
||||||
|
=== DefaultManagedInferenceProvider
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Wraps a `LlamaCppInferenceProvider` and delegates lifecycle calls to a `ModelManager`. Implements `ManagedInferenceProvider` by delegation.
|
||||||
|
|
||||||
|
=== LlamaProcess
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Manages the OS process lifecycle for the `llama-server` binary. Supports `start()` and `stop()` with I/O redirection to a log file.
|
||||||
|
* **fields**: `command` (process arguments), `logFile` (stdout/stderr destination)
|
||||||
|
|
||||||
|
=== LlamaCppTokenizer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: `Tokenizer` implementation that calls the llama.cpp `/tokenize` HTTP endpoint.
|
||||||
|
|
||||||
|
=== GbnfGrammarConverter
|
||||||
|
* **kind**: internal object
|
||||||
|
* **purpose**: Converts a `JsonSchema` (object type) to a GBNF grammar string used by llama.cpp for constrained generation. Supports required and optional keys.
|
||||||
|
|
||||||
|
=== LlamaCppApiModels
|
||||||
|
* **kind**: file-level serializable classes
|
||||||
|
* **purpose**: DTOs for the OpenAI-compatible chat completion API: `ChatCompletionRequest`, `ChatMessage`, `ChatCompletionResponse`, `Choice`, `Usage`, `TokenizeRequest`, `TokenizeResponse`.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:* None. Inference requests come through `InferenceProvider.infer(request)` which is called by the core inference layer.
|
||||||
|
|
||||||
|
*Outbound:*
|
||||||
|
* `ModelLoadedEvent` — emitted when a model is successfully loaded and health-checked
|
||||||
|
* `ModelUnloadedEvent` — emitted when a model is unloaded
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:inference` — `InferenceProvider`, `InferenceRequest`, `InferenceResponse`, `ProviderHealth`, `Tokenizer`, `CapabilityScore`, `ResponseFormat`, `FinishReason`, `ToolCallRequest`, `ToolDefinition`
|
||||||
|
* `core:events` — `ModelLoadedEvent`, `ModelUnloadedEvent`, `ProviderId`, `SessionId`
|
||||||
|
* `core:tools` — `ToolDefinition`
|
||||||
|
* `infra:inference:commons` — `ModelManager`, `ManagedInferenceProvider`, `ModelDescriptor`, `ModelLoadException`, `ResidencyMode`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `DefaultModelManager` ensures single-model-at-a-time — load replaces the current model by killing and restarting the subprocess
|
||||||
|
* Health check must pass before a new model is considered loaded
|
||||||
|
* Model ID mismatch on unload throws `ModelLoadException`
|
||||||
|
* `GbnfGrammarConverter` only supports `type: "object"` schemas — other types will throw
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-inference-llama-cpp, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-inference-llama-cpp.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `GbnfGrammarConverter` only supports object-type schemas with string/number properties
|
||||||
|
* `DefaultManagedInferenceProvider.load()` uses `as? ManagedInferenceProvider` safe cast — returns null then throws explicit `ModelLoadException`, not `ClassCastException`
|
||||||
|
* `DefaultModelManager` has an unused `eventStore` constructor parameter (used only for `LivenessScanner` in CAS, not relevant here)
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should the `llama-server` binary path be configurable per-environment?
|
||||||
|
* Should process stdout/stderr be exposed for debugging beyond file logging?
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
= infra-persistence
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Provides concrete `EventStore` implementations for persisting and replaying the event log. Supports in-memory storage (for testing and ephemeral sessions) and SQLite-backed durable storage. Also provides a live artifact repository that subscribes to events and maintains an up-to-date in-memory view of artifact state per session.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Implement the `EventStore` interface for both transient and durable storage
|
||||||
|
* Enforce duplicate event ID detection and sequence ordering
|
||||||
|
* Provide reactive subscriptions via `Flow<StoredEvent>` per session and globally
|
||||||
|
* Maintain an artifact state projection that stays current via event subscription
|
||||||
|
* Support transactional batch appends for atomic event persistence
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define event serialization — uses `JsonEventSerializer` from `core:events`
|
||||||
|
* Does not implement CAS content-addressable storage — that belongs to `infra:artifacts-cas`
|
||||||
|
* Does not handle event compaction or retention
|
||||||
|
* Does not implement cross-session querying beyond `allSessionIds()`
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== InMemoryEventStore
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thread-safe, in-memory event store backed by `ConcurrentHashMap`. Supports duplicate detection via `seenEventIds` set and session-scoped sequence numbers.
|
||||||
|
* **fields**: `streams` (per-session event lists), `sequences` (per-session sequence counters), `globalSeq` (global counter), `seenEventIds` (dedup set)
|
||||||
|
|
||||||
|
=== SqliteEventStore
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Durable event store backed by a SQLite database with WAL mode. Uses JDBC directly. Emits events to reactive flows after append.
|
||||||
|
* **fields**: `connection` (JDBC SQLite connection), `jsonSerializer` (serialization bridge), `artifactStore` (used for `flushBefore` coordination)
|
||||||
|
|
||||||
|
=== LiveArtifactRepository
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Subscribes to an `EventStore` per session and maintains an up-to-date in-memory `ConcurrentHashMap` of `ArtifactState` values. Supports lookup by session, stage, and artifact ID.
|
||||||
|
* **fields**: `artifactCache` (session → artifactId → state), `stageIndex` (session → artifactId → stageId), `subscriptions` (session → coroutine Job), `projector` (reduces events to state)
|
||||||
|
|
||||||
|
=== JDBCHelper
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Utility providing `Connection.transaction()` inline helper with automatic commit/rollback.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:*
|
||||||
|
* `ArtifactCreatedEvent`, `ArtifactValidatingEvent`, `ArtifactValidatedEvent`, `ArtifactRejectedEvent`, `ArtifactSupersededEvent`, `ArtifactArchivedEvent`, `ArtifactRelationshipAddedEvent` — consumed by `LiveArtifactRepository` to maintain artifact projections
|
||||||
|
* `NewEvent` — appended to event stores via `append()` / `appendAll()`
|
||||||
|
|
||||||
|
*Outbound:*
|
||||||
|
* `StoredEvent` — emitted via `Flow<StoredEvent>` subscriptions after each append
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:events` — `EventStore`, `StoredEvent`, `NewEvent`, all artifact event payloads, `EventId`, `SessionId`
|
||||||
|
* `core:artifacts` — `ArtifactProjector`, `ArtifactReducer`, `ArtifactState`, `ArtifactRepository`
|
||||||
|
* `core:artifactstore` — `ArtifactStore` (used by `SqliteEventStore` for flush coordination)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Duplicate `eventId` values are rejected with an error
|
||||||
|
* Session sequence numbers are strictly monotonic — violations throw `error()`
|
||||||
|
* `InMemoryEventStore.append()` is synchronized per session for thread safety
|
||||||
|
* `SqliteEventStore` wraps appends in a JDBC transaction and coordinates with `ArtifactStore.flushBefore()`
|
||||||
|
* `LiveArtifactRepository` lazily subscribes on first query and never unsubscribes
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-persistence, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-persistence.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `SqliteEventStore` holds an open JDBC `Connection` for the lifetime of the application
|
||||||
|
* No connection pooling or retry logic for SQLite busy conditions
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should event stores support snapshot-based recovery for long event streams?
|
||||||
|
* Should there be a read-replica event store for projections to avoid write-contention?
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
= infra-scheduler
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module reserved for task scheduling infrastructure — delayed execution, cron-like triggers, and queue management. Currently contains only a `Module` marker object with no implementation.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Intended: schedule delayed or periodic task execution
|
||||||
|
* Intended: manage execution queues and worker pools
|
||||||
|
* Intended: persist scheduled tasks across restarts
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define scheduling contracts — that belongs to `core`
|
||||||
|
* Does not implement workflow orchestration — that belongs to `core:kernel`
|
||||||
|
* Does not provide inference routing — that belongs to `core:inference`
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Module
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Module marker for the Gradle subproject; no behavior.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* No events are consumed or emitted. This is a placeholder.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No integration points. The module has no dependencies beyond `core:events`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
None — module is empty.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-scheduler, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-scheduler.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
Module is a placeholder — no scheduler infrastructure exists.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will scheduling be event-driven (e.g., timer events) or time-based (cron)?
|
||||||
|
* Should scheduling be embedded (coroutine-based) or backed by an external queue (Kafka, RabbitMQ)?
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
= infra-security
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module reserved for security infrastructure — authentication, authorization, credential management, and secure communication. Currently contains only a `Module` marker object with no implementation.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Intended: provide authentication adapters (API keys, OAuth, etc.)
|
||||||
|
* Intended: enforce authorization checks on tool execution and API access
|
||||||
|
* Intended: manage credential storage and rotation
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define security contracts — that belongs to `core`
|
||||||
|
* Does not implement approval gates — that belongs to `core:approvals`
|
||||||
|
* Does not provide sandboxing — that belongs to `infra:tools`
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Module
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Module marker for the Gradle subproject; no behavior.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* No events are consumed or emitted. This is a placeholder.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No integration points. The module has no dependencies beyond `core:events`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
None — module is empty.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-security, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-security.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
Module is a placeholder — no security infrastructure exists.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* What authentication mechanism(s) will be supported (API key, JWT, mTLS)?
|
||||||
|
* Will credentials be stored in the event store or in an external secrets manager?
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
= infra-telemetry
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Placeholder module reserved for telemetry infrastructure — metrics collection, structured logging, tracing export, and monitoring integration. Currently contains only a `Module` marker object with no implementation.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Intended: implement metrics collection (counters, gauges, histograms)
|
||||||
|
* Intended: provide log shipping and structured log formatting
|
||||||
|
* Intended: export distributed tracing data
|
||||||
|
* Intended: integrate with monitoring backends (Prometheus, OpenTelemetry, etc.)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define observability contracts — that belongs to `core:observability`
|
||||||
|
* Does not implement application-level instrumentation — that belongs to individual modules
|
||||||
|
* Does not store or query telemetry data long-term
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== Module
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Module marker for the Gradle subproject; no behavior.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* No events are consumed or emitted. This is a placeholder.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
No integration points. The module has no dependencies beyond `core:events`.
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
None — module is empty.
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-telemetry, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-telemetry.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
Module is a placeholder — no telemetry infrastructure exists.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Which telemetry backend will be used (Prometheus, OpenTelemetry, Datadog)?
|
||||||
|
* Will telemetry be synchronous (side-channel) or event-sourced?
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
= infra-tools-filesystem
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Provides three filesystem tool implementations for the Correx tool system: `FileReadTool` (read file content with optional line range), `FileWriteTool` (write or delete files with artifact capture via CAS), and `FileEditTool` (append, replace, or patch file content). All tools enforce path allowlisting and validate parameters before execution.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Read file contents with optional 1-indexed line range slicing
|
||||||
|
* Write content to files, optionally capturing artifacts to the CAS store
|
||||||
|
* Delete files on disk
|
||||||
|
* Edit files via three modes: append (add to end), replace (exact-target string replacement), patch (apply unified diff via external `patch` command)
|
||||||
|
* Validate paths against configured allowlists
|
||||||
|
* Report affected paths for sandbox backup/diff tracking
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Do not implement sandboxed execution — that belongs to `infra:tools` (`SandboxedToolExecutor`)
|
||||||
|
* Do not define tool contracts — that belongs to `core:tools`
|
||||||
|
* Do not manage file permissions beyond optional POSIX mode setting
|
||||||
|
* Do not perform approval gating
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== FileReadTool
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Reads a file from disk within allowed paths, with optional start/end line range. Tier T1.
|
||||||
|
* **fields**: `allowedPaths` (set of permitted root directories)
|
||||||
|
|
||||||
|
=== FileWriteTool
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Writes content to a file or deletes a file, validates path against allowlist, optionally captures a `FileWrittenArtifact` to the CAS store. Tier T2.
|
||||||
|
* **fields**: `allowedPaths`, `artifactStore`, `materializingWriter`, `sandboxRoot`, `workingDir`
|
||||||
|
|
||||||
|
=== FileEditTool
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Edits a file via three operations: `append`, `replace` (exact single-occurrence string replacement), or `patch` (applies unified diff via external `patch` binary). Tier T3.
|
||||||
|
* **fields**: `allowedPaths`, `workingDir`
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:* `ToolRequest` with parameters (`path`, `content`, `operation`, etc.)
|
||||||
|
|
||||||
|
*Outbound:* None directly. Event emission is handled by the wrapping `SandboxedToolExecutor`. The tools return `ToolResult.Success` or `ToolResult.Failure` synchronously.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:tools.contract` — `Tool`, `ToolExecutor`, `ToolResult`, `FileAffectingTool`, `ToolCapability`, `ValidationResult`
|
||||||
|
* `core:approvals` — `Tier`
|
||||||
|
* `core:artifactstore` — `ArtifactStore`
|
||||||
|
* `core:artifacts` — `MaterializingArtifactWriter`, `MaterializationResult`, `FileWrittenArtifact`, `FileWrittenPayload`
|
||||||
|
* `infra:artifacts-cas` — used by `FileWriteTool` for CAS artifact storage (via writer interface)
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* All tools validate path against allowed paths — if `allowedPaths` is empty, validation fails ("No paths are allowed")
|
||||||
|
* `FileWriteTool` prevents directory traversal via path normalization
|
||||||
|
* `FileEditTool.replace()` requires exactly one occurrence of the target string — zero or multiple occurrences produce a failure
|
||||||
|
* `FileEditTool.patch()` requires the external `patch` utility to be available
|
||||||
|
* All tools run on `Dispatchers.IO` for blocking filesystem operations
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-tools-filesystem, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-tools-filesystem.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `FileEditTool.patch()` shells out to the `patch` binary — not available on all platforms; no error if `patch` is missing beyond a failed `ProcessBuilder`
|
||||||
|
* `FileWriteTool.storeArtifact()` silently logs on materialization failure rather than failing the tool call
|
||||||
|
* `FileEditTool.replace()` reads the entire file into memory — unsuitable for very large files
|
||||||
|
* `FileWriteTool` and `FileEditTool` have duplicated path resolution logic
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `FileEditTool` support a pure-Kotlin patching implementation to remove `patch` dependency?
|
||||||
|
* Should line-range edits (insert before/after line N) be added?
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
= infra-tools
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Provides the concrete tool execution infrastructure: a sandboxed executor that wraps tool calls with file backup/restore, diff computation, and event emission; a dispatching executor that routes tool requests to registered tools; a default tool registry; a shell command tool; and a tool configuration system that maps config to enabled tool instances.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Wrap tool execution with sandbox directory management and file backup/restore
|
||||||
|
* Compute unified diffs for file-affecting tool results
|
||||||
|
* Emit `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`, and `ToolExecutionFailedEvent`
|
||||||
|
* Dispatch tool requests by name to registered tool implementations
|
||||||
|
* Register tools by name via `DefaultToolRegistry`
|
||||||
|
* Execute shell commands with timeout, stdout/stderr capture, and executable allowlisting
|
||||||
|
* Convert `ToolConfig` to a list of `Tool` implementations via `buildTools()`
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not define tool contracts — that belongs to `core:tools`
|
||||||
|
* Does not implement filesystem tools — that belongs to `infra:tools:filesystem`
|
||||||
|
* Does not enforce approval gating — that belongs to `core:approvals`
|
||||||
|
* Does not perform workflow-level tool routing
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== SandboxedToolExecutor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Decorator around a `ToolExecutor` that handles per-invocation working directories, file backup/restore for file-affecting tools, diff computation, and event emission. Operates on `Dispatchers.IO`.
|
||||||
|
* **fields**: `delegate` (inner executor), `registry`, `eventDispatcher`, `workDir` (default `/tmp/correx-sandbox`)
|
||||||
|
|
||||||
|
=== DispatchingToolExecutor
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Routes a `ToolRequest` to the appropriate `ToolExecutor` by resolving the tool name from a `ToolRegistry`.
|
||||||
|
|
||||||
|
=== DefaultToolRegistry
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Simple map-based `ToolRegistry` built from a vararg or list of `Tool` instances. Keyed by `tool.name`.
|
||||||
|
|
||||||
|
=== ShellTool
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Implements both `Tool` and `ToolExecutor` for shell command execution. Supports executable allowlisting, configurable timeout (default 30s), and working directory. Validates that `argv` is non-empty and the executable is allowed. Tier T2.
|
||||||
|
* **fields**: `allowedExecutables`, `timeoutMs` (default 30000), `workingDir`
|
||||||
|
|
||||||
|
=== DiffUtil
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Computes unified-diff strings from old and new file contents using LCS-based edit detection. Used by `SandboxedToolExecutor` to record changes in tool receipts.
|
||||||
|
|
||||||
|
=== ToolConfig
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Configuration structure for enabling and parameterizing tools. Contains sub-configs for shell, file_read, file_write, and file_edit tools.
|
||||||
|
* **fields**: `shell`, `fileRead`, `fileWrite`, `fileEdit`
|
||||||
|
|
||||||
|
=== ToolConfig.buildTools()
|
||||||
|
* **kind**: extension function
|
||||||
|
* **purpose**: Converts a `ToolConfig` into a `List<Tool>` by creating instances for each enabled sub-config.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*Inbound:* `ToolRequest` (from core tools layer)
|
||||||
|
|
||||||
|
*Outbound:*
|
||||||
|
* `ToolExecutionStartedEvent` — emitted before delegate execution begins
|
||||||
|
* `ToolExecutionCompletedEvent` — includes `ToolReceipt` with exit code, output, metadata, affected entities, duration, tier, and diff
|
||||||
|
* `ToolExecutionFailedEvent` — emitted on execution failure
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:events` — `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`, `ToolExecutionFailedEvent`, `ToolReceipt`, `ToolRequest`, `ToolInvocationId`, `SessionId`
|
||||||
|
* `core:tools` — `Tool`, `ToolExecutor`, `ToolResult`, `FileAffectingTool`, `ToolRegistry`, `ToolCapability`, `ValidationResult`
|
||||||
|
* `core:approvals` — `Tier`
|
||||||
|
* `infra:tools:filesystem` — `FileReadTool`, `FileWriteTool`, `FileEditTool`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `SandboxedToolExecutor` computes `affectedPaths` once (before delegate execution) to avoid double-invocation divergence
|
||||||
|
* File backup is performed before delegate execution; on failure, originals are restored; on success, backups are deleted
|
||||||
|
* `ShellTool` rejects executables not in the allowed set with `ValidationResult.Invalid`
|
||||||
|
* All tools default to `enabled = false` in configuration
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-tools, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-tools.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `SandboxedToolExecutor` cleanup (`cleanWorkingDir`, `restoreOrClean`) is best-effort and may leave files on IO errors
|
||||||
|
* `FileEditTool.patch()` spawns an external `patch` process — not a pure-Kotlin implementation
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `SandboxedToolExecutor` support configurable sandbox root resolution?
|
||||||
|
* Should tool timeout be configurable per-tool-type rather than per-call?
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
= infra-workflow
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Loads workflow definitions from TOML files and from the filesystem, converting them into the core `WorkflowGraph` and providing prompt content loading. The `TomlWorkflowLoader` parses a TOML workflow specification (stages, transitions, conditions) and validates structural correctness. The `FileSystemPromptLoader` resolves prompt file paths locally and from a config directory.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Parse TOML workflow definitions into `WorkflowGraph` objects
|
||||||
|
* Validate workflow structure: declared start stage exists, all stage/transition references resolve, artifact dependencies are satisfiable
|
||||||
|
* Convert transition condition specs from TOML to `TransitionCondition` objects (always_true, artifact_present/absent/validated, variable_equals, all_of, any_of, not)
|
||||||
|
* Load prompt content from filesystem paths (local path first, then config directory)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not orchestrate workflow execution — that belongs to `core:kernel`
|
||||||
|
* Does not define `WorkflowGraph`, `StageConfig`, or `TransitionEdge` types — those come from `core:transitions`
|
||||||
|
* Does not render or template prompt content
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== WorkflowLoader
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for loading a `WorkflowGraph` from a filesystem path.
|
||||||
|
|
||||||
|
=== TomlWorkflowLoader
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Parses TOML workflow files using Jackson's `TomlMapper` and validates the resulting graph structure.
|
||||||
|
* **fields**: `registry` (`ArtifactKindRegistry` for resolving artifact kind names)
|
||||||
|
|
||||||
|
=== ConditionSpec
|
||||||
|
* **kind**: data class
|
||||||
|
* **purpose**: Intermediate deserialization model for transition conditions from TOML. Converted to `TransitionCondition` via `ConditionFactory`.
|
||||||
|
* **fields**: `type`, `artifactId`, `key`, `value`, `conditions` (nested), `condition` (single nested for `not`)
|
||||||
|
|
||||||
|
=== ConditionFactory (file-level `toCondition()` extension)
|
||||||
|
* **kind**: extension functions
|
||||||
|
* **purpose**: Maps `ConditionSpec.type` strings to `TransitionCondition` implementations. Supports all_of, any_of, not (composite), always_true, artifact_present, artifact_absent, artifact_validated, and variable_equals.
|
||||||
|
|
||||||
|
=== PromptLoader
|
||||||
|
* **kind**: interface
|
||||||
|
* **purpose**: Contract for loading prompt content by path string.
|
||||||
|
|
||||||
|
=== FileSystemPromptLoader
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Resolves prompt files: tries the given path directly, then falls back to `~/.config/correx/prompts/<path>`.
|
||||||
|
* **fields**: `configDir` (default `~/.config/correx/prompts`)
|
||||||
|
|
||||||
|
=== PromptNotFoundException
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thrown when a prompt file cannot be found at any searched location.
|
||||||
|
|
||||||
|
=== WorkflowValidationException
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thrown on workflow structural validation failures (missing stages, duplicate transitions, unsatisfied artifact dependencies).
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*None.* The workflow loader is invoked at session start to load configuration — it does not interact with the event store.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `core:transitions.graph` — `WorkflowGraph`, `StageConfig`, `TransitionEdge`, `TransitionCondition`
|
||||||
|
* `core:transitions.conditions` — `AlwaysTrue`, `AllOf`, `AnyOf`, `Not`, `ArtifactPresent`, `ArtifactAbsent`, `ArtifactValidated`, `VariableEquals`
|
||||||
|
* `core:artifacts.kind` — `ArtifactKindRegistry`, `DefaultArtifactKindRegistry`, `TypedArtifactSlot`
|
||||||
|
* `core:events.types` — `StageId`, `TransitionId`, `ArtifactId`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Start stage must be declared in the stages list
|
||||||
|
* Every transition `from` and `to` reference must name a declared stage (except `to = "done"` which is the terminal sentinel)
|
||||||
|
* Transition IDs must be unique within a workflow file
|
||||||
|
* Every artifact listed in a stage's `needs` must be produced by some stage's `produces`
|
||||||
|
* Unknown condition types in TOML produce a `WorkflowValidationException`
|
||||||
|
* Prompt resolution searches the literal path first, then the config directory
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, infra-workflow, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/infra-workflow.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ConditionSpec` `conditions` and `condition` fields are populated by the TOML parser for their respective condition types (`all_of`/`any_of` use `conditions`; `not` uses `condition`) and consumed by the `toCondition()` extension in `ConditionFactory`. The spec's recursive schema is exercised in tests but the TOML parser only populates flat fields — nested specs are constructed programmatically.
|
||||||
|
* `TomlWorkflowLoader` uses Jackson `TomlMapper` with `FAIL_ON_UNKNOWN_PROPERTIES` disabled, so typos in workflow files may silently produce empty defaults.
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should composite conditions (`all_of`, `any_of`, `not`) actually be parseable from TOML, or are they for programmatic use only?
|
||||||
|
* Should workflow validation include cycle detection in the transition graph?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= interface-api
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for an API interface module that will define the public HTTP/REST contract exposed by Correx server applications. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future API surface definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, interface-api, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/interface-api.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will this module define OpenAPI specs, Ktor route contracts, or generated client stubs?
|
||||||
|
* Should this module be removed and its purpose absorbed by `:apps:server`?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= interface-cli
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for a CLI interface module that would define the abstract command contract shared by CLI applications. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future CLI interface abstractions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, interface-cli, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/interface-cli.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should this module define the `CliktCommand` subclasses as interfaces, or is `:apps:cli` sufficient as the sole CLI application?
|
||||||
|
* Could this module host a shared CLI argument parser or validation logic?
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
= interface-sdk
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for an SDK module that would define a programmatic client library for interacting with Correx from external applications. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for a future Correx SDK
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, interface-sdk, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/interface-sdk.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will the SDK be Kotlin-only or multi-language (e.g., generated from OpenAPI)?
|
||||||
|
* Should the SDK provide both blocking and coroutine-based APIs?
|
||||||
|
* Should the SDK implement reconnection logic, or leave that to callers?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= plugin-compressors
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external compression plugins that implement custom context compression strategies beyond the built-in compressor. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future compressor plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-compressors, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-compressors.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will compressor plugins implement the `ContextCompressor` interface from `:core:context`?
|
||||||
|
* What compression strategies (semantic chunking, summary, retrieval-based) are planned for plugins?
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
= plugin-policies
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external policy plugins that define custom approval policies, gating rules, and compliance checks beyond the built-in approval engine. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future policy plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-policies, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-policies.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will policy plugins integrate with the `ApprovalEngine` interface from `:core:approvals`?
|
||||||
|
* What policy types (role-based, time-based, compliance) are planned?
|
||||||
|
* Will policies be evaluated before or after the approval engine's built-in checks?
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
= plugin-providers
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external inference provider plugins that connect Correx to LLM backends beyond the built-in llama.cpp adapter. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future inference provider plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-providers, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-providers.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will provider plugins implement the `InferenceProvider` interface from `:core:inference`?
|
||||||
|
* How will provider plugins register themselves with the `DefaultProviderRegistry`?
|
||||||
|
* Will there be a provider marketplace or discovery mechanism?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= plugin-stages
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external stage-type plugins that define custom stage execution behaviors beyond the built-in stage model. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future stage plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-stages, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-stages.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will stage plugins define custom execution strategies (parallel, branching, looping)?
|
||||||
|
* How will stage plugins integrate with the `OrchestratorEngines` wiring?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= plugin-tools
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external tool plugins that extend the Correx tool system beyond the built-in infrastructure tools (shell, file write, file edit). Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future tool plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-tools, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-tools.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will tool plugins conform to a SPI interface, or be loaded via service loader configuration?
|
||||||
|
* What isolation mechanism (subprocess, container, WASM) will external tool plugins use?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= plugin-transitions
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external transition evaluation plugins that implement custom transition conditions beyond the built-in evaluator. Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future transition plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-transitions, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-transitions.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will transition plugins implement the `TransitionConditionEvaluator` functional interface?
|
||||||
|
* What condition types (time-based, output-based, external signals) are planned?
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
= plugin-validators
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
No source files present. This directory is reserved for external validation plugins that implement custom validation logic beyond the built-in validators (e.g., artifact payload validation). Currently a structural placeholder.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Declares the module structure for future validator plugin definitions
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* This module has no source code, types, or runtime behavior
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
*No types defined.*
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
None. Module has no source files.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* No current dependencies or dependents
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Not applicable — module is empty
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, plugin-validators, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/plugin-validators.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* This module contains no implementation — it is an empty placeholder
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Will validator plugins conform to the `Validator` functional interface from `:core:validation`?
|
||||||
|
* Should validators be loaded at startup or discovered dynamically?
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
= test-approvals
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Tests for the approval subsystem: tier immutability, grant semantics, mode-based approval, approval engine edge cases, and the decoupling of approval decisions from tool execution.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Verify that tier levels are immutable and cannot be downgraded
|
||||||
|
* Test grant semantics — who can approve what at which tier
|
||||||
|
* Test mode-based approval (auto-approve, prompt, deny) behavior
|
||||||
|
* Test approval engine edge cases (concurrent requests, expired requests, missing state)
|
||||||
|
* Verify that approval decisions do not couple to tool execution (decoupling invariant)
|
||||||
|
* Test the `ApprovalTrigger` mechanism for policy-driven approval initiation
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not test the `ApprovalCoordinator` server module (tested via `:apps:server`'s own tests)
|
||||||
|
* Does not test the WebSocket approval flow end-to-end
|
||||||
|
* Does not test the approval UI components
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== TierImmutabilityTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that once a tier level is set, it cannot be modified — tier is an immutable value object.
|
||||||
|
|
||||||
|
=== GrantSemanticsTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests that the approval engine correctly grants or denies requests based on tier of the approving entity vs. tier of the request.
|
||||||
|
|
||||||
|
=== ModeApprovalTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests approval mode behavior: auto-approve mode skips user interaction, prompt mode requires explicit decision, deny mode rejects everything.
|
||||||
|
|
||||||
|
=== ApprovalEngineEdgeCasesTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests edge cases in the approval engine: duplicate request IDs, expired timestamps, null fields, concurrent resolution.
|
||||||
|
|
||||||
|
=== NoExecutionCouplingTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that approval decisions are evaluated independently of tool execution — rejecting an approval does not require understanding the tool's execution plan.
|
||||||
|
|
||||||
|
=== ApprovalTriggerTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests the approval trigger mechanism — which actions/conditions should trigger an approval request.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* Constructed `DomainApprovalRequest` and `DomainApprovalDecision` instances.
|
||||||
|
|
||||||
|
*outbound:* Assertions on approval state (approved, rejected, pending) and event outcomes.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:approvals` — `DefaultApprovalEngine`, `ApprovalEngine`, `Tier`, `ApprovalOutcome`, `ApprovalStatus`, approval model types
|
||||||
|
* `:core:events` — approval-related event payload types
|
||||||
|
* `:testing:fixtures` — `ApprovalFixtures`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Tier levels are immutable — once assigned to a request or tool, the tier cannot change
|
||||||
|
* Policy denial is absolute — an approval cannot override a policy denial
|
||||||
|
* Approval decisions are independent of tool execution details
|
||||||
|
* Duplicate resolution of the same request is idempotent
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-approvals, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-approvals.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ApprovalTriggerTest` behavior depends on how triggers are defined — if the trigger mechanism is not yet fully implemented, the test may be skeletal
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should there be tests for approval escalation (promoting a request to a higher tier if unresolved)?
|
||||||
|
* Should the approval engine support batch approval (approve N requests at once)?
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
= test-contracts
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Contract tests that validate the behavioral guarantees of core domain types. Ensures that value objects, model classes, and domain interfaces conform to their documented contracts across serialization, equality, and behavior.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Validate `WorkflowGraph`, `StageId`, `TransitionId` construction and invariants
|
||||||
|
* Test `InferenceProvider` contract (routing, health check, caching)
|
||||||
|
* Test `ValidationReport` contract and serialization
|
||||||
|
* Test `ContextSnapshot` construction and serialization
|
||||||
|
* Test orchestration model types (`OrchestrationConfig`, `WorkflowResult`, `StageOutcome`, `RetryPolicy`)
|
||||||
|
* Provide concurrent testing utilities (`ConcurrencyRunner`, `DeterministicBarrier`)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not test full integration between modules
|
||||||
|
* Does not test infrastructure adapters
|
||||||
|
* Does not test UI or application layers
|
||||||
|
|
||||||
|
== key types (test utilities)
|
||||||
|
|
||||||
|
=== ConcurrencyRunner
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Runs a block concurrently across N threads for M iterations using a `CountDownLatch` for synchronized start. Used for concurrency safety contract tests.
|
||||||
|
|
||||||
|
=== DeterministicBarrier
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Thin wrapper over `java.util.concurrent.CyclicBarrier` for synchronizing test threads at known points.
|
||||||
|
|
||||||
|
=== WorkflowGraphTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests `WorkflowGraph` construction, stage lookup, transition validation, and serialization round-trips.
|
||||||
|
|
||||||
|
=== InferenceProviderContractTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Defines the contract that all `InferenceProvider` implementations must satisfy (request handling, health check, capability reporting).
|
||||||
|
|
||||||
|
=== DefaultInferenceRouterTest / DefaultInferenceRouterCacheTest
|
||||||
|
* **kind**: JUnit 5 test classes
|
||||||
|
* **purpose**: Tests routing strategy, provider selection, and cache invalidation behavior.
|
||||||
|
|
||||||
|
=== EventsTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests event payload construction, metadata, and serialization contracts.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* Constructed domain objects and serialized JSON strings.
|
||||||
|
|
||||||
|
*outbound:* Assertions on equality, serialization round-trips, exception types, and behavioral contracts.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:transitions` — `WorkflowGraph`, `StageConfig`, `TransitionEdge`
|
||||||
|
* `:core:inference` — `InferenceProvider`, `DefaultInferenceRouter`, `ProviderHealth`
|
||||||
|
* `:core:validation` — `ValidationReport`, validation model types
|
||||||
|
* `:core:events` — event payload types, `EventMetadata`
|
||||||
|
* `:core:context` — `ContextSnapshot`
|
||||||
|
* `:core:kernel` — orchestration config and result types
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* All serializable types survive a JSON encode/decode round-trip without data loss
|
||||||
|
* `StageId` and `TransitionId` are valid when non-blank
|
||||||
|
* `InferenceProvider.healthCheck()` never throws for healthy providers
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-contracts, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-contracts.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `ConcurrencyRunner` does not assert on thread safety — it only provides concurrent execution scaffolding; the test must supply assertions
|
||||||
|
* `DeterministicBarrier` does not have a timeout — a stalled test thread will hang indefinitely
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should contract tests be parameterized to run against all implementations of each interface?
|
||||||
|
* Should JSON schema validation be added to serialization contract tests?
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
= test-deterministic
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Tests verifying the determinism invariants of the Correx system: that event replay, context compression, approval decisions, router state, and validation pipelines produce identical results given identical inputs, independent of timing, execution order, or environment.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Verify that session replay is deterministic — identical event streams produce identical state
|
||||||
|
* Verify that context compression is deterministic — identical inputs produce identical compressed outputs
|
||||||
|
* Verify that the approval engine produces deterministic outcomes — same request + same config = same decision
|
||||||
|
* Verify that the router facade, projector, and reducer produce deterministic state
|
||||||
|
* Verify that the context pack builder produces deterministic results given the same inputs
|
||||||
|
* Verify token budget enforcement — requests exceeding the budget are rejected consistently
|
||||||
|
* Verify graph validation determinism — same graph produces same validation errors
|
||||||
|
* Verify validation pipeline short-circuit behavior — short-circuit outcomes are consistent and deterministic
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not test performance or timing characteristics
|
||||||
|
* Does not test non-deterministic components (real inference providers, network I/O)
|
||||||
|
* Does not test application layers or UI
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== SessionReplayDeterminismTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that replaying the same event stream multiple times produces identical session state each time.
|
||||||
|
|
||||||
|
=== ContextCompressionDeterminismTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that the context compressor produces identical output when run multiple times with the same inputs and budget.
|
||||||
|
|
||||||
|
=== ApprovalEngineDeterminismTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that the approval engine returns the same decision given the same request and configuration across multiple invocations.
|
||||||
|
|
||||||
|
=== RouterFacadeTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests the router facade for deterministic behavior — identical user inputs produce identical router responses (when router is mocked).
|
||||||
|
|
||||||
|
=== RouterProjectorTest / RouterReducerTest / RouterContextBuilderTest
|
||||||
|
* **kind**: JUnit 5 test classes
|
||||||
|
* **purpose**: Test router projection, reduction, and context building determinism.
|
||||||
|
|
||||||
|
=== GraphValidatorTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that workflow graph validation produces consistent results for the same graph topology.
|
||||||
|
|
||||||
|
=== ValidationPipelineShortCircuitTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that validation pipeline short-circuiting behaves consistently — a fatal validator always stops the pipeline regardless of invocation order.
|
||||||
|
|
||||||
|
=== DefaultContextPackBuilderTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that the context pack builder produces identical context packs given identical input events and budgets.
|
||||||
|
|
||||||
|
=== TokenBudgetEnforcementTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Verifies that token budget enforcement is deterministic — requests over budget are consistently rejected or truncated.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* Constructed events, configurations, and inputs that are fed through the component under test multiple times.
|
||||||
|
|
||||||
|
*outbound:* Assertions that identical outputs are produced for each invocation.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:sessions` — `DefaultSessionReducer`, `SessionProjector`, `EventReplayer`
|
||||||
|
* `:core:context` — `DefaultContextPackBuilder`, `DefaultContextCompressor`, `TokenBudget`
|
||||||
|
* `:core:approvals` — `DefaultApprovalEngine`
|
||||||
|
* `:core:router` — `RouterFacade`, `RouterProjector`, `RouterReducer`
|
||||||
|
* `:core:transitions` — `GraphValidator`
|
||||||
|
* `:core:validation` — `ValidationPipeline`, `Validator`
|
||||||
|
* `:testing:fixtures` — `EventFixtures`, `InferenceFixtures`, `ApprovalFixtures`, `ContextFixtures`
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* All projected state is deterministic — given the same event stream, the output is always identical
|
||||||
|
* All compression operations are deterministic — given the same entries and budget, the output is always the same
|
||||||
|
* The approval engine is deterministic — given the same input, the decision is always the same
|
||||||
|
* Token budget enforcement produces deterministic pass/fail outcomes per request
|
||||||
|
* Validation pipeline short-circuit is deterministic — the order of validators does not affect the short-circuit outcome
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-deterministic, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-deterministic.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Determinism tests rely on mocked components — they verify the mock's determinism, not the real runtime's
|
||||||
|
* Some tests may pass with mocks but fail with real implementations (e.g., real tokenizer adds nondeterminism)
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should there be a dedicated determinism test that runs against the full production stack (minus network)?
|
||||||
|
* Should CI run determinism tests with multiple seed values to increase coverage?
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
= test-fixtures
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Shared test fixture library providing reusable mock implementations, factory functions, and test doubles for all Correx testing modules. Reduces duplication and ensures consistent test setup across the project.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Provide factory functions for constructing `NewEvent`, `StoredEvent`, and domain events with minimal boilerplate
|
||||||
|
* Provide mock inference provider (`MockInferenceProvider`) with configurable behavior (fixed response, delay, forced failure)
|
||||||
|
* Provide mock tokenizer (`MockTokenizer`) with deterministic character-based token counting
|
||||||
|
* Provide test tool implementations (`EchoTool`, `FailingTool`, `RejectedTool`) at different tier levels
|
||||||
|
* Provide mock event replayers and session repositories for kernel tests
|
||||||
|
* Provide workflow graph fixtures (`simpleGraph`, `threeStageGraph`)
|
||||||
|
* Provide approval fixtures (approval request factory, cycle policy validator)
|
||||||
|
* Provide cycle detection fixtures
|
||||||
|
* Provide context compression fixtures (no-op compressor, simple builder)
|
||||||
|
* Provide inference fixtures (fixed router, failing router, context pack, generation config)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not contain any test assertions or test cases (except `MockToolsTest` which validates the tools themselves)
|
||||||
|
* Does not provide mocks for infrastructure adapters (SQLite, HTTP, etc.)
|
||||||
|
* Does not provide full in-memory event store replacement
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== EventFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Factory methods `newEvent()` and `stored()` that produce `NewEvent` and `StoredEvent` instances with sensible defaults. Reduces boilerplate in event-sourcing tests.
|
||||||
|
|
||||||
|
=== MockInferenceProvider
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Configurable fake `InferenceProvider`. Three mutually exclusive modes: `fixedResponse` (returns immediately), `artificialDelayMs` (suspends before responding), `forcedFailure` (throws). Tracks `inferCallCount`.
|
||||||
|
|
||||||
|
=== MockTokenizer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Character-based `Tokenizer` approximation: 1 token ≈ 4 characters. Deterministic and not model-accurate. Suitable for contract and budget tests.
|
||||||
|
|
||||||
|
=== EchoTool / FailingTool / RejectedTool
|
||||||
|
* **kind**: classes implementing `Tool`
|
||||||
|
* **purpose**: Test tools at tiers T0, T2, and T4 respectively. `EchoTool` validates successfully, `FailingTool` fails validation, `RejectedTool` validates but is configured for rejection.
|
||||||
|
|
||||||
|
=== MockEventReplayer / MockSessionRepository
|
||||||
|
* **kind**: classes
|
||||||
|
* **purpose**: Test doubles for kernel module dependencies. Return pre-configured or default states by session ID.
|
||||||
|
|
||||||
|
=== InferenceFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: Factory methods for `InferenceProvider`, `InferenceRouter`, `InferenceRequest`, `InferenceResponse`, `ContextPack`, `GenerationConfig`.
|
||||||
|
|
||||||
|
=== ApprovalFixtures
|
||||||
|
* **kind**: top-level functions
|
||||||
|
* **purpose**: `request()` factory for `DomainApprovalRequest`; `cyclePolicyMissingValidator()` for validation pipeline tests.
|
||||||
|
|
||||||
|
=== WorkflowFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: `simpleGraph()` factory creating a two-stage A->B workflow graph.
|
||||||
|
|
||||||
|
=== TransitionFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: `alwaysTrueEvaluator()`, `simpleResolver()`, `threeStageGraph()` for transition resolution tests.
|
||||||
|
|
||||||
|
=== CycleFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: `simpleCycle()` for cycle detection tests.
|
||||||
|
|
||||||
|
=== ContextFixtures
|
||||||
|
* **kind**: object
|
||||||
|
* **purpose**: `simpleCompressor()` (no-op) and `simpleBuilder()` for context builder tests.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
Not applicable — this module provides construction helpers, not event processing.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:events` — `EventPayload`, `NewEvent`, `StoredEvent`, `EventMetadata`
|
||||||
|
* `:core:inference` — `InferenceProvider`, `InferenceRouter`, `InferenceRequest`, `InferenceResponse`, `Tokenizer`
|
||||||
|
* `:core:tools` — `Tool` interface, `Tier`
|
||||||
|
* `:core:approvals` — `DomainApprovalRequest`, domain approval types
|
||||||
|
* `:core:transitions` — `WorkflowGraph`, `TransitionResolver`, `TransitionConditionEvaluator`
|
||||||
|
* `:core:context` — `ContextPack`, `ContextCompressor`, `DefaultContextPackBuilder`
|
||||||
|
* `:core:sessions` — `SessionState`, `EventReplayer`
|
||||||
|
* `:core:kernel` — `OrchestrationState`
|
||||||
|
* `:core:validation` — `Validator`, validation model types
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* `MockTokenizer.tokenize("")` returns empty list; `countTokens("")` returns 0
|
||||||
|
* `MockTokenizer` uses 4-char chunking — `countTokens("abcd")` = 1, `countTokens("abcde")` = 2
|
||||||
|
* `EventFixtures.stored()` defaults to `sessionSequence = sequence` (1:1 mapping)
|
||||||
|
* `MockInferenceProvider.inferCallCount` increments atomically per call
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-fixtures, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-fixtures.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* `MockTokenizer` is not model-accurate — tests using it for token budgets may pass with the mock but fail with a real tokenizer
|
||||||
|
* `EventFixtures.stored()` sets `sessionSequence = sequence` by default, which may not match production behavior where sequences diverge after resets or gaps
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should `MockInferenceProvider` support streaming response simulation?
|
||||||
|
* Should the fixtures include an in-memory event store implementation for tests?
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
= test-integration
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Integration tests that exercise end-to-end workflows spanning multiple core modules and infrastructure components. Verifies that components wire together correctly and produce the expected outcomes across subsystem boundaries.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Test the `SessionOrchestrator` end-to-end: from session start through stage transitions, tool execution, and completion
|
||||||
|
* Test the `ValidationPipeline` end-to-end: from payload submission through all validators to final validation report
|
||||||
|
* Verify that events flow correctly between subsystems (reducer -> store -> projector -> repository)
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not test with a real SQLite/Postgres event store (uses in-memory store)
|
||||||
|
* Does not test real inference providers or tool executors
|
||||||
|
* Does not test UI or application layers
|
||||||
|
* Does not test network-level integration (HTTP, WebSocket)
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== SessionOrchestratorIntegrationTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests full orchestration lifecycle: creates a `WorkflowGraph`, starts a session via `DefaultSessionOrchestrator`, verifies stage progression, tool invocation events, and session completion.
|
||||||
|
|
||||||
|
=== ValidationPipelineIntegrationTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests the validation pipeline with multiple validators, verifying that each validator is invoked and that the aggregated validation report is correct.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* Constructed `NewEvent` instances and orchestration commands submitted to the orchestrator or validation pipeline.
|
||||||
|
|
||||||
|
*outbound:* Assertions on `StoredEvent` sequences emitted to the event store, final state from projections, and validation reports.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:kernel` — `DefaultSessionOrchestrator`, `OrchestrationConfig`, `OrchestratorEngines`
|
||||||
|
* `:core:events` — `EventStore`, event payload types
|
||||||
|
* `:core:validation` — `ValidationPipeline`, `Validator`, validation model types
|
||||||
|
* `:core:sessions` — `DefaultSessionRepository`, `SessionProjector`
|
||||||
|
* `:core:transitions` — `DefaultTransitionResolver`, `WorkflowGraph`
|
||||||
|
* `:testing:fixtures` — mock providers, tools, compressors, repositories, and event replayers
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Orchestrator follows the configured workflow graph — stages execute in declared order
|
||||||
|
* The event store contains all events produced during orchestration
|
||||||
|
* Validation pipeline evaluates all registered validators
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-integration, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-integration.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Tests use mocked engines, not real inference or tool execution — real end-to-end behavior is not validated
|
||||||
|
* In-memory event store may not surface ordering or persistence bugs present in production stores
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should integration tests include a real SQLite event store to verify write/read ordering?
|
||||||
|
* Should there be a separate integration test for the server module's HTTP and WebSocket routing?
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
= test-kernel
|
||||||
|
|
||||||
|
== purpose
|
||||||
|
|
||||||
|
Tests for the core kernel orchestration logic: retry coordination and inference replay. Verifies that the `DefaultRetryCoordinator` correctly manages retry attempts and that inference events replay to the correct provider state.
|
||||||
|
|
||||||
|
== responsibilities
|
||||||
|
|
||||||
|
* Test `DefaultRetryCoordinator` retry policy enforcement (max attempts, backoff, exhaustion)
|
||||||
|
* Test `ReplayInferenceProvider` to ensure inference events replay correctly through the inference projector
|
||||||
|
|
||||||
|
== non-responsibilities
|
||||||
|
|
||||||
|
* Does not test the full session orchestrator lifecycle
|
||||||
|
* Does not test infrastructure retry mechanisms (e.g., HTTP retry)
|
||||||
|
* Does not test UI or application layers
|
||||||
|
|
||||||
|
== key types
|
||||||
|
|
||||||
|
=== MockOrchestrationEventReplayer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Test double implementing `EventReplayer<OrchestrationState>`. Returns pre-configured states by session ID; defaults to `RUNNING` with `stage-1`.
|
||||||
|
|
||||||
|
=== MockSessionEventReplayer
|
||||||
|
* **kind**: class
|
||||||
|
* **purpose**: Test double implementing `EventReplayer<SessionState>`. Returns pre-configured states by session ID; defaults to `ACTIVE`.
|
||||||
|
|
||||||
|
=== DefaultRetryCoordinatorTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests retry coordination logic — max retries, exponential backoff duration, retry exhaustion behavior.
|
||||||
|
|
||||||
|
=== ReplayInferenceProviderTest
|
||||||
|
* **kind**: JUnit 5 test class
|
||||||
|
* **purpose**: Tests that inference events streamed through the replay mechanism produce correct `InferenceState`.
|
||||||
|
|
||||||
|
== event flow
|
||||||
|
|
||||||
|
*inbound:* Programmatically constructed events and state objects injected into mock replayers.
|
||||||
|
|
||||||
|
*outbound:* Assertions on retry coordinator state (attempt count, delay values) and replayed inference state.
|
||||||
|
|
||||||
|
== integration points
|
||||||
|
|
||||||
|
* `:core:kernel` — `DefaultRetryCoordinator`, orchestration state and event types
|
||||||
|
* `:core:inference` — `InferenceProjector`, `InferenceState`, `InferenceRepository`
|
||||||
|
* `:core:sessions` — `SessionState`, `SessionStatus`
|
||||||
|
* `:core:events` — event payload types, `EventReplayer` interface
|
||||||
|
* `:testing:fixtures` — mock replayers and state builders
|
||||||
|
|
||||||
|
== invariants
|
||||||
|
|
||||||
|
* Retry coordinator respects max attempt limits — exceeding them marks the operation as exhausted
|
||||||
|
* Event replay produces the same inference state regardless of the number of replay passes
|
||||||
|
|
||||||
|
== PlantUML diagram
|
||||||
|
|
||||||
|
[plantuml, test-kernel, "png"]
|
||||||
|
----
|
||||||
|
include::../../diagrams/test-kernel.puml[]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== known issues
|
||||||
|
|
||||||
|
* Mock replayers provide pre-canned states rather than true event-sourced replay — tests do not verify the replay machinery itself, only the components that consume replay output
|
||||||
|
|
||||||
|
== open questions
|
||||||
|
|
||||||
|
* Should the tests also cover the `DefaultRetryCoordinator` integration with the event store?
|
||||||
|
* Should there be a full `EventStore` + `RetryCoordinator` integration test here?
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user