= 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?
