= 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/`. * **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?