Commit Graph

6 Commits

Author SHA1 Message Date
Jordan Ritter 62474be1dd fix(showcase/ci): make a cancelled build slot a first-class outcome
`job.status` for a matrix slot is success|failure|cancelled, but the
per-slot writer laundered cancelled into `skipped` before publishing its
result, so a slot killed by `timeout-minutes` became indistinguishable
from one that legitimately never built.

That erased the only signal that could tell a partially-cancelled fleet
build from a clean one. GitHub's status functions cannot recover it:
`cancelled()` is documented as "returns true if the workflow was
canceled" (workflow-scoped, and FALSE for a leg-only cancel), and a
cancelled ancestor is not a FAILED ancestor so `failure()` is false too.

Add `cancelled` to the BuildOutcome contract, add `cancelledSet()`, and
have the aggregator publish `any_cancelled` + `cancelled_services`
alongside `any_success`. `successSet` still excludes cancelled slots, so
the redeploy intersection is unchanged — a slot that pushed no image
still cannot enter the redeploy CSV.
2026-07-25 10:04:58 -07:00
Jordan Ritter e5d2d47a04 fix(showcase): canonicalize build-result service names (trim) + reject array payloads 2026-05-29 11:45:12 -07:00
Jordan Ritter 8c603fb84a fix(showcase): harden build-outputs (reject empty/duplicate service, unify validation, fail loud)
- Reject empty/whitespace-only service in parseBuildOutputs,
  mergeBuildResultFiles, and buildResultArtifactName (was only
  buildResultArtifactName, and only for length===0).
- mergeBuildResultFiles now throws on duplicate service names across
  slots so an upstream dispatch-name collision surfaces instead of
  letting a failure+success pair spuriously look like a success in
  successSet (fail-loud discipline).
- Derive BuildOutcome union and VALID_STATUSES set from a single
  BUILD_OUTCOMES as-const tuple plus a compile-time exhaustiveness
  assignment so they cannot drift.
- Extract validateServiceBuildResult shared validator used by both
  parseBuildOutputs (context: 'entry[i]') and mergeBuildResultFiles
  (context: 'slot[i]') — one set of rules, one error format, both
  now include the offending index.
- Trim shouldRedeployStaging JSDoc to what/why only (dropped the
  external-caller enumeration) and note in the module header that
  the single-result.json-per-slot invariant is enforced workflow-side,
  not by the parser. shouldRedeployStaging([]) === false behavior
  unchanged.

Tests: 16 -> 23 passing, all new red-then-green; tsc 0.
2026-05-29 11:45:12 -07:00
Jordan Ritter 5901629c98 feat(showcase): add shouldRedeployStaging guard predicate
Add shouldRedeployStaging(results) to showcase/scripts/lib/build-outputs.ts.
Returns true iff at least one service finished as 'success'. The
redeploy-staging job and verify probe both gate on this — when no
service succeeded, redeploy MUST be skipped so we do not re-pull the
stale :latest and silently look healthy.

Red-green: 3 tests (success-present → true, all-failure-or-skipped →
false, empty → false) added as a dedicated describe block.

Per plan-E E-5a/E-5b.
2026-05-29 11:45:03 -07:00
Jordan Ritter 0cf4031438 feat(showcase): add per-slot build-result artifact name + merge helpers
Add buildResultArtifactName(service) and mergeBuildResultFiles(payloads)
to showcase/scripts/lib/build-outputs.ts so each matrix slot in
showcase_build.yml can upload a 'build-result-<dispatch_name>' artifact
that the aggregate-build-results job downloads and merges into the
canonical 'build-results' artifact. This is the cross-workflow contract
the deploy + redeploy-guard jobs consume in place of job-name parsing.

Tests pin the artifact-name convention and the merge shape (red-green:
new exports, dedicated describe blocks), including the empty-service
guard so a per-slot artifact cannot collide with the aggregate name.

Per plan-E E-4c/E-4d.
2026-05-29 11:45:03 -07:00
Jordan Ritter e2b0c418e8 feat(showcase): add build-outputs parser for structured build-matrix results 2026-05-29 11:45:02 -07:00