Files
poimen/tasks/T3.2-workflowformat-trait-yaml-and-json.md
T

3.9 KiB
Raw Blame History

T3.2 — WorkflowFormat trait + YAML and JSON

Field Value
Phase P3 — Workflow as data
Size M — 1 to 3 days
Status Not started
Flags
Spec inlined below
Blocks

Goal

Formats are plugins. Ship YAML and JSON; prove a third format needs no kernel change.

Facts (inlined — no spec read needed)

pub trait WorkflowFormat: Send + Sync {
    fn extensions(&self) -> &[&str];
    fn parse(&self, src: &[u8]) -> Result<WorkflowDef, ParseError>;
}
  • A user wanting a DSL, Starlark, or a database row implements the trait.
  • Validation and canonicalization live in the kernel and run on the IR (T3.1, T3.3), so a new format inherits every check without reimplementing one. A format that does its own validation has forked the rules.
  • Because the version hash is over the canonical IR, the same workflow in two formats must produce the same WorkflowVersion — that equality is the test that the boundary is in the right place.
  • Defaults ship working; every default is a port. A user who wants built-in behaviour writes no code; a user who wants their own writes an impl, not a fork.

Steps

  1. Define the trait exactly as above. parse returns the IR and nothing else — no side effects, no registration, no validation.
  2. Implement YamlFormat and JsonFormat over serde. Both target the same IR types from T3.1.
  3. Build the format registry keyed by extension. Resolution is by extension, with a clear error when two formats claim the same one.
  4. Make ParseError carry position information where the format supplies it — this is the only error type that legitimately knows about source text.
  5. Write the same workflow in YAML and in JSON; assert equal WorkflowVersion.
  6. In a separate test crate, implement a third trivial format (for example TOML or a line-based DSL) against the public API only. If it needs anything pub(crate), the trait is short — fix the trait, not the test.

Acceptance

  • The same workflow expressed in both formats produces the same WorkflowVersion.
  • A third format added in a test crate needs no kernel change.

Verify

Harness: a separate test crate outside the workspace depending only on the published API — that crate's existence is the deliverable, not a convenience.

Integration testtests/it_format_equivalence.rs plus third-format-crate/:

  1. Express one non-trivial workflow (all five StepKinds) in YAML and in JSON.
  2. Parse both; assert identical WorkflowVersion.
  3. Assert the parsed IRs are byte-identical after canonicalization.
  4. In the external crate, implement a third format (TOML or a line DSL) against the public API only. Build it. If it needs any pub(crate) item, the trait is short — that is a finding, not a test workaround.
  5. Register the third format and run the same validation suite (T3.3) against it; assert it inherits every check with no new code.
  6. Extension collision: register two formats claiming .yaml; assert a clear error rather than last-one-wins.

Command: cargo test -p workflow format_equivalence && cargo test --manifest-path third-format-crate/Cargo.toml

False pass:

  • The third-format crate living inside the workspace, where it can reach internals. It then passes while the public API is unusable — which is the entire thing being tested.
  • Step 5 omitted: a format that does its own validation passes steps 14 and has quietly forked the rules.

Traps

  • Format-specific validation creeping into parse. Two formats then disagree about what is legal.
  • Leaking serde types into the IR, which makes the third-format test need a serde dependency it should not have.

Background (not required to do this task): rust-agentic-sys.md §1, §4.2 · rust-agentic-task.md