fix(lint): AB#3159 — ban PowerShell bareword Jinja renders in workflow YAMLs - #362
Merged
Merged
Conversation
…w YAMLs ## Bug class AB#3156 Bug 1 (PR #354 / commit e48a31d) was a `command: pwsh` script in plan-level.yaml that did: $hasTopics = {{ (architect.output.research_needs.topics | default([]) | length > 0) | string | lower }} Jinja renders `true` / `false` (Python bool → lowercase string). PowerShell parses bareword `true` as a cmdlet name, finds nothing, errors silently — `$hasTopics` ends up `$null`. Serialized to JSON as `null`; downstream Jinja then evaluates `null != false` and routes to the wrong branch. Killed an AB#3127 dogfood relaunch. The required form is to wrap the Jinja render in a string literal and string-compare (or cast) the result: $hasTopics = '{{ (...) | string | lower }}' -eq 'true' # bool $count = [int]'{{ ... | length }}' # int Type is `[bool]` / `[int]` regardless of what Jinja emits, and a malformed render fails loudly instead of silently nulling. ## What this PR ships 1. `tests/lint-pwsh-jinja-bareword.ps1` — Pester-tested lint that scans every `.conductor/registry/workflows/*.yaml`, parses via `powershell-yaml`, walks each `command: pwsh` agents `args:` list, and flags any line matching `\$\w+\s*=\s*\{\{[^}]+\}\}` where the `{{` is not preceded by a quote. Whitelist marker `# bareword-ok: <reason>` on the same or preceding line opts a line out (use sparingly). 2. `tests/lint-pwsh-jinja-bareword.Tests.ps1` — 16 Pester tests covering the canonical safe form, double-quoted form, array-literal form, `[int]'...'` cast form, the AB#3156 Bug 1 shape, integer-bareword shape, folded-block-scalar (`>-`) shape, multi-agent aggregation, line-number reporting, both whitelist marker positions, GitHub annotations format, and a live-tree gate. 3. `.github/workflows/ci.yml` — new `Lint workflow YAMLs (PowerShell + Jinja bareword booleans)` step sequenced after `Install powershell-yaml` (the lint requires that module). 4. Audit + remediation of every other in-tree case the lint surfaced. PR #354 fixed the boolean case in plan-level.yaml; this PR remediates 8 integer-bareword cases (defense-in-depth — bareword integers happen to parse correctly today, but every Jinja render should be quoted-and-cast so a future render-time anomaly fails loudly): - actionable.yaml merge_evidence_pr.$prNumber - apex-driver.yaml wave_loop_summary.$failed, .$total - implement-merge-group.yaml primary_completer.$taskId - plan-level.yaml research_loop_counter.$count open_questions_counter.$count, .$maxLoops auto_approve_plan_pr.$prNumber All converted to `[int]'{{ … }}'`. ## Verification - `pwsh -NoProfile -File tests/lint-pwsh-jinja-bareword.ps1` exits 0 against `main` after these remediations. - `Invoke-Pester -Path tests/lint-pwsh-jinja-bareword.Tests.ps1`: 16/16 green. - `Invoke-Pester -Path tests/harness/run-scenarios.Tests.ps1`: 5/5 green (architect_research_loop, cascade_remedy_no_stale, close_out_happy_path, research_happy_path). - Existing lints (lint-jinja-resolver, lint-strict-undefined, lint-prose-children, lint-type-agnostic) all clean. - lint-conductor-validate.Tests.ps1 + workflow-terminal-satisfied-observable.Tests.ps1: 13/13 green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem
added a commit
that referenced
this pull request
May 14, 2026
…Item 2) (#370) Adds Phase 2.5 to Invoke-PolyphonySdlc.ps1: a wig show $ApexId --output json pre-flight that refuses to dispatch when the target work item is in a terminal state (Done, Closed, Removed, Resolved). Closes the AB#3127 reproducer documented in the AB#3165 epic: re-running the apex driver against a completed work item produces a false-positive empty_merge_group_structural_violation at scope_revise_cap_gate, wasting 15+ min before hitting the cap. Outputs of run N pollute inputs of run N+1 because the work this item describes is already on main; the implementer agent has nothing to do; the scope reviewer mistakes the empty MG for an upstream classifier failure. Bypassed by: * -Intent resume / replan (operator is intentionally rejoining) * -SkipStateCheck (escape hatch for transition / debug scenarios) Refusal message points the operator at the canonical remediation: wig state $ApexId 'To Do' then re-launch. Tests: 11 new tests in scripts/Invoke-PolyphonySdlc.Tests.ps1 covering each terminal state, both bypass intents, the SkipStateCheck switch, and the error-message remediation pointers. Existing 32 tests pass transparently via a wig.cmd shim injected by New-BareRepoFixture (default state: 'To Do', overridable via $env:TWIG_FAKE_STATE). Empirical reproducer: AB#3159 dogfood 2026-05-13 launched against a work item whose implementation had landed on main 2h earlier via PR #362. Implementer correctly produced an empty branch; scope reviewer hit the cap gate after 3 cycles. This change would have refused the launch upfront — provided the operator had marked the item Done first. The To-Do-but-already-merged variant remains an open class of bug (filed under AB#3165 Item 4: branch-tip zero-diff short-circuit at apex dispatch time). Co-authored-by: Daniel Green <dangreen@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes AB#3159 (F1 of Epic AB#3158 — Convert prose conventions to typed enforcement).
The bug class
AB#3156 Bug 1 (PR #354 / commit
e48a31d) was acommand: pwshscript inplan-level.yamlthat did:$hasTopics = {{ (architect.output.research_needs.topics | default([]) | length > 0) | string | lower }}Jinja renders
true/false(Python bool → lowercase string). PowerShell parses barewordtrueas a cmdlet name, finds nothing, errors silently —$hasTopicsends up$null. Serialized to JSON asnull; downstream Jinja then evaluatesnull != falseand routes to the wrong branch. Killed an AB#3127 dogfood relaunch.The required form wraps the Jinja render in a string literal and string-compares (or casts) the result:
Type is
[bool]/[int]regardless of what Jinja emits, and a malformed render fails loudly instead of silently nulling.What this PR ships
tests/lint-pwsh-jinja-bareword.ps1— Pester-tested lint that scans every.conductor/registry/workflows/*.yaml, parses viapowershell-yaml, walks eachcommand: pwshagentsargs:list, and flags any line matching\$\w+\s*=\s*\{\{[^}]+\}\}where the{{is not preceded by a quote. Whitelist marker# bareword-ok: <reason>on the same or preceding line opts a line out (use sparingly).tests/lint-pwsh-jinja-bareword.Tests.ps1— 16 Pester tests covering the canonical safe form, double-quoted form, array-literal form,[int]cast form, the AB#3156 Bug 1 shape, integer-bareword shape, folded-block-scalar (>-) shape, multi-agent aggregation, line-number reporting, both whitelist marker positions, GitHub annotations format, and a live-tree gate..github/workflows/ci.yml— newLint workflow YAMLs (PowerShell + Jinja bareword booleans)step sequenced afterInstall powershell-yaml(the lint requires that module).Audit + remediation of every other in-tree case the lint surfaced. PR fix(plan-level): AB#3156 — research path StrictUndefined + PowerShell bareword #354 fixed the boolean case in
plan-level.yaml; this PR remediates 8 integer-bareword cases (defense-in-depth — bareword integers happen to parse correctly today, but every Jinja render should be quoted-and-cast so a future render-time anomaly fails loudly):$prNumber$failed,$total$taskId$count$count,$maxLoops$prNumberAll converted to
[int]'{{ … }}'.Verification
pwsh -NoProfile -File tests/lint-pwsh-jinja-bareword.ps1exits 0 againstmainafter these remediations.Invoke-Pester -Path tests/lint-pwsh-jinja-bareword.Tests.ps1: 16/16 green.Invoke-Pester -Path tests/harness/run-scenarios.Tests.ps1: 5/5 green (architect_research_loop, cascade_remedy_no_stale, close_out_happy_path, research_happy_path).lint-conductor-validate.Tests.ps1+workflow-terminal-satisfied-observable.Tests.ps1: 13/13 green.