Skip to content

Cross-layer validation: close the workflow ↔ conductor ↔ polyphony-verb contract gap #172

Description

@PolyphonyRequiem

Cross-layer validation: workflow ↔ conductor ↔ polyphony-verb

Problem

Three independent layers participate in every workflow step, and nothing
checks that they agree until a dogfood run actually exercises the step:

  1. Workflow YAML (.conductor/registry/workflows/*.yaml) — references
    verb output fields like {{ derive_ancestor_chain.output.parent_item_id | default(0) }}
    and calls Jinja filters/functions. conductor validate checks schema
    shape only. Hand-written lint-*.ps1 greps in
    .conductor/registry/tests/ check spelling but cannot resolve a
    reference back to its producer.
  2. Conductor Jinja runtime (conductor/src/conductor/executor/template.py) —
    custom filter set (_default_filter accepts only 2 positional args),
    strict_undefined on. Lints can require Jinja constructs that
    conductor does not actually expose.
  3. Polyphony verb output — C# DTOs serialized via
    PolyphonyJsonContext (src/Polyphony/PolyphonyJsonContext.cs:172,
    DefaultIgnoreCondition = WhenWritingNull). Nullable fields are
    silently elided from the JSON wire shape, so a schema-present field
    can be a runtime-missing key.

Each layer is well-tested in isolation. The contract between them is
checked only by execution.

Evidence — recent dogfood-discovered bugs

Bug PR(s) Class
#6 #168 Field-name drift: YAML read type_loader.output.type_name; verb emits type.
#7 #169 Workflow + lint required severities_at_or_above(...) Jinja function; conductor never registered it. Fix: precompute as a verb output field.
#8 #170, #171 derive-ancestor-chain.parent_item_id (int?) elided by WhenWritingNull on root path; strict_undefined raised before default(0) could fire. Then default(0, true) crashed because conductor's default filter is 2-arg.
#159 family #159, tracked in #165 polyphony CLI exits 0 on unrecognized verbs/args, so YAML→CLI signature drift is invisible.

Each one only surfaced when execution reached the offending step. All four
were mechanically detectable at lint time given the right cross-layer
metadata.

The catalog at docs/polyphony-state-effects-catalog.md § "Open questions
/ gaps" already documents these as a class but offers no fix track.

Proposed fix shape — four prongs

Tracked as child issues. Each is independently shippable.

  1. Verb output schema registry[Validation gap] Verb output schema registry generated from PolyphonyJsonContext #173. Generated from
    PolyphonyJsonContext source. Per-field WhenWritingNull vs Never
    behaviour encoded. Published as verb-output-schemas.json artifact
    consumable by lint and humans. Wire shape is the load-bearing detail
    (bug [PG-1] Core Infrastructure and Routing Types #8).
  2. YAML Jinja-reference resolver lint[Validation gap] Workflow YAML Jinja-reference resolver lint #175. For every
    {{ X.output.field.path }} in workflow YAML, look up X's verb in
    the registry, walk the field path, fail lint if missing or if the
    field can be omitted at runtime without a default() guard. Catches
    bug Feature/2582 pg 3 #6 (name drift) and bug [PG-1] Core Infrastructure and Routing Types #8 (omit-when-null without guard).
    Depends on [Validation gap] Verb output schema registry generated from PolyphonyJsonContext #173.
  3. Conductor Jinja inventory pin[Validation gap] Conductor Jinja inventory pin and lint enforcement #174. Generated manifest of
    filters and globals conductor's TemplateRenderer actually exposes,
    with signatures. Lint refuses any Jinja construct outside that set.
    Maintained by a small probe against the pinned conductor version.
    Catches bug Feature/2582 pg 4 #7 (nonexistent function) and bug [PG-1] Core Infrastructure and Routing Types #8 fixup (wrong-arity
    filter call).
  4. Polyphony CLI strict-modepolyphony CLI exits 0 on unrecognized args (silent-failure root cause) #165 (pre-existing, adopted as the
    prong-4 child). Exit non-zero on unrecognized
    verbs/subcommands/options. No upstream deps; ship first.

What this is NOT

  • Not changing how workflows are authored. Same Jinja, same YAML.
  • Not introducing a DSL. The registry is generated, not hand-maintained.
  • Not blocking on conductor changes. Prongs 1, 2, 4 are owned by
    polyphony. Prong 3 needs only a probe script run against the conductor
    version polyphony already targets (min_polyphony_version in workflow
    metadata; no formal conductor SHA pin today — that's part of [Validation gap] Conductor Jinja inventory pin and lint enforcement #174's
    design).

Acceptance

Children

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions