Skip to content

fix: reject duplicated top-level workflow output sections - #2

Open
eisen0419 wants to merge 1 commit into
AgwaB:mainfrom
eisen0419:fix/reject-duplicate-output-sections
Open

fix: reject duplicated top-level workflow output sections#2
eisen0419 wants to merge 1 commit into
AgwaB:mainfrom
eisen0419:fix/reject-duplicate-output-sections

Conversation

@eisen0419

Copy link
Copy Markdown

Problem

docs/usage.md states that artifact-graph task output is parsed strictly as exactly one <control>, followed by one <analysis>, followed by one <refs>, with no outside prose. In @agwab/pi-workflow@0.9.0, repeated section tags can evade that contract: collectSections records at most one match for each expected section, so validateSectionCounts never observes a count above one, and the sanitized-output fallback can discard a repeated trailing section before re-parsing.

A qualification run demonstrated the effect: an output with two complete <control> blocks was accepted with outputValidation.valid=true, no output retries, and a completed run (workflow_mrwrw4lv_5f2c2d). By comparison, an invalid control JSON type followed the normal invalid-output retry path and ended as workflow_output_invalid_exhausted.

Reproduction

Use an otherwise valid protocol response and append a second section:

<control>{"schema":"stage-control-v1","digest":"first"}</control>
<analysis>valid analysis</analysis>
<refs>[]</refs>
<control>{"schema":"stage-control-v1","digest":"second"}</control>

Before this change, parseWorkflowOutputForBundle sanitizes the first three sections and returns valid output.

Fix

During layout validation, count canonical opening tags that appear outside every matched section span. When a tag is observed more than once, emit the existing duplicate_section validation issue with the section name and observed count, which routes the output into the existing invalid-output retry/rejection path. Sanitized-output recovery is also stopped when any canonical tag is duplicated, so recovery cannot erase the evidence before validation.

Counting only outside matched spans preserves the documented tolerance for literal tags inside section content: protocol text quoted inside control JSON strings (covered by the existing test "workflow output parser tolerates literal closing tags inside JSON strings") is still accepted, because those occurrences sit inside the matched <control> span.

Tests

test/unit/output-duplicate-section-rejection.test.mjs adds regression coverage:

  • repeated <control> block → duplicate_section, output invalid (both parseWorkflowOutput and parseWorkflowOutputForBundle);
  • repeated <analysis> block → duplicate_section;
  • trailing repeated <refs> block → still rejected (absorbed into the refs span and fails refs JSON validation);
  • exactly-one output of each kind → unchanged, zero issues;
  • literal opening tags inside section content → still tolerated.

npm run validate passes (713/713 unit tests, typecheck, script and public-surface checks).

Compatibility

Valid outputs containing exactly one required section of each kind are unchanged. Outputs that repeat <control>, <analysis>, or <refs> at the top level now correctly enter the existing invalid-output retry/rejection path, matching the documented strict protocol.

Repeated <control>/<analysis>/<refs> blocks could evade the documented
exactly-one layout rule: collectSections records at most one match per
section and sanitized recovery could drop a repeated trailing block
before re-parsing.

Count canonical opening tags outside every matched section span during
layout validation and emit the existing duplicate_section issue when a
tag repeats, routing the output into the invalid-output retry path.
Literal tags inside section content (e.g. protocol text quoted in
control JSON strings) remain tolerated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant