🤖 Generated by the Agentic Engineer
Evidence. validateDRWorkflow establishes the DR rebuild's publication wiring by scanning workflow text, not by parsing it. containsLine(job, "uses: ./.github/actions/deploy-prod/publish-platform-manifests") is satisfied by that string appearing anywhere in the job — including inside a comment or a block scalar. Reported by CodeRabbit as a P1 during review of #2939, and verified here: the symbols carrying this (validateDRWorkflow, extractJob, containsExactLine, containsLine) are untouched by that PR, so this is pre-existing rather than a regression it introduced.
Impact. The disaster-recovery route can be pointed at a different publisher while the validator still finds the expected text and reports success — so DR delivery could publish through logic this contract never checks, which is the exact drift the shared-action requirement exists to prevent. The same reasoning applies to the credential assertions (ghcr-token, hcloud-token), which are matched the same way and so are not proven to sit on the publishing step at all.
Why this is its own issue. #2939 converted the direct-push and merge-queue routes from text scanning to parsing and enforced-step checks. This is the same class at the third route, outside that PR's diff, and #2939 is already at review round 10 on a single concern.
Expected behaviour. Parse dr-rebuild.yaml, locate jobs.rebuild.steps, and require an enforced step (neither skipped nor failure-suppressed) whose uses value equals nestedPublisherAction — reusing requireEnforcedStep/usesAction, which #2939 already introduced for exactly this. Assert with.ghcr-token and with.hcloud-token on that same step rather than anywhere in the job.
Acceptance criteria.
Size. Small — the parsing helpers and requireEnforcedStep already exist from #2939; this is applying them to the third route.
Evidence.
validateDRWorkflowestablishes the DR rebuild's publication wiring by scanning workflow text, not by parsing it.containsLine(job, "uses: ./.github/actions/deploy-prod/publish-platform-manifests")is satisfied by that string appearing anywhere in the job — including inside a comment or a block scalar. Reported by CodeRabbit as a P1 during review of #2939, and verified here: the symbols carrying this (validateDRWorkflow,extractJob,containsExactLine,containsLine) are untouched by that PR, so this is pre-existing rather than a regression it introduced.Impact. The disaster-recovery route can be pointed at a different publisher while the validator still finds the expected text and reports success — so DR delivery could publish through logic this contract never checks, which is the exact drift the shared-action requirement exists to prevent. The same reasoning applies to the credential assertions (
ghcr-token,hcloud-token), which are matched the same way and so are not proven to sit on the publishing step at all.Why this is its own issue. #2939 converted the direct-push and merge-queue routes from text scanning to parsing and enforced-step checks. This is the same class at the third route, outside that PR's diff, and #2939 is already at review round 10 on a single concern.
Expected behaviour. Parse
dr-rebuild.yaml, locatejobs.rebuild.steps, and require an enforced step (neither skipped nor failure-suppressed) whoseusesvalue equalsnestedPublisherAction— reusingrequireEnforcedStep/usesAction, which #2939 already introduced for exactly this. Assertwith.ghcr-tokenandwith.hcloud-tokenon that same step rather than anywhere in the job.Acceptance criteria.
validateDRWorkflowresolves the rebuild job and its publisher step through the YAML parser, not string containment.usesis matched by equality, and the two credentials are asserted on that step.if:skip, nocontinue-on-error).usesreplaced while the expected text survives in a comment; the same in a block scalar; credentials moved to a different step; the publisher step skipped/failure-suppressed. Each must be refused.id-token/packages/attestations: write) move to parsed lookups too, or the issue records why they stay textual.Size. Small — the parsing helpers and
requireEnforcedStepalready exist from #2939; this is applying them to the third route.