Skip to content

fix(showcase): harden staging→prod promote reliability (snapshot skip + honest verify-prod signal)#5322

Merged
jpr5 merged 2 commits into
mainfrom
fix/showcase-promote-reliability
Jun 8, 2026
Merged

fix(showcase): harden staging→prod promote reliability (snapshot skip + honest verify-prod signal)#5322
jpr5 merged 2 commits into
mainfrom
fix/showcase-promote-reliability

Conversation

@jpr5

@jpr5 jpr5 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Two robustness/signal defects in the showcase staging→prod promote, both surfaced while investigating today's docs promote failure (run 27144525566). Shared theme: harden the promote so one odd service can't abort the whole run, and so the alert never reports a green it didn't earn.

Fix #1 — a single thrown ServiceInstance not found no longer aborts the whole promote

showcase/bin/railway · PromoteCommand/build_snapshot

build_snapshot enumerates every project service and queries each one's serviceInstance. The only guard was next if inst.nil? — it handled a null result but not a thrown GraphQL: ServiceInstance not found error (a half-deleted service that still appears in the project service list but has no instance in that env). That error bubbled to Railway.run's top-level rescue GraphQL::Error and aborted the entire promote with an opaque exit 2 before any preflight/divergence logic ran. In run 27144525566 a single odd/transient service threw this and killed the docs promote.

  • The per-service loop now tolerates a thrown ServiceInstance not found for an individual service exactly like the nil case: log + skip that one service, keep going.
  • The rescue is scoped narrowly to that one message — every other GraphQL failure (auth, rate-limit, schema drift, transient 5xx) still propagates fail-loud.

Fix #2verify-prod reports skipped (not a misleading success) when it skipped

.github/workflows/showcase_promote.yml · verify-prod + notify

When the promote fails, the succeeded-service set is empty, so verify-prod hits its skip branch and exit 0. The GitHub job result is therefore success, and the #oss-alerts Slack line rendered verify-prod=success even though prod was never probed — a misleading green.

  • verify-prod now exports a status job output: success after a real probe passes, skipped on the empty-CSV skip.
  • notify reads that output (not the raw job result) so the Slack line accurately reads verify-prod=skipped vs success vs failure. A real probe failure / contract violation exits non-zero (job result failure, status never written) and the display falls back to the job result.
  • Slack formatting is unchanged.

Tests (red-green)

  • Fix WIP: almost usable v0.1 #1 (showcase/bin/spec/test_snapshot_graphql.rb): a single thrown ServiceInstance not found is skipped (healthy services still snapshot); an unrelated GraphQL error still raises. Verified red before the fix, green after. test_snapshot_ivar_lint.rb line-pins renumbered to match.
  • Fix turbo refactor #2: the display mapping is extracted into showcase/scripts/verify-prod-display.sh (mirroring promote-fleet.sh) so it is unit-testable; showcase/scripts/__tests__/verify-prod-display.bats covers success / skipped / failure / cancelled / fallback. Added the new script to the showcase_validate.yml shellcheck step.

Test plan

  • ruby showcase/bin/spec/all_tests.rb — 129 runs, 0 failures
  • bats showcase/scripts/__tests__/ — 18 ok (incl. 6 new)
  • shellcheck on both promote scripts — clean
  • actionlint (+shellcheck) on showcase_promote.yml — clean
  • zizmor --min-severity low (CI config) on changed workflows — clean
  • CI green on the PR

jpr5 added 2 commits June 8, 2026 10:06
…omote snapshot

build_snapshot enumerates every project service and queries each one's
serviceInstance. The only guard was `next if inst.nil?` — it handled a
NULL result but not a THROWN `GraphQL: ServiceInstance not found` error
(a half-deleted service that still appears in the project service list
but has no instance in the env). That error bubbled to Railway.run's
top-level `rescue GraphQL::Error` and aborted the ENTIRE promote with an
opaque exit 2 before any preflight/divergence logic ran (run 27144525566
killed the docs promote this way).

Scope the rescue narrowly to ONLY the per-service "ServiceInstance not
found" message — log+skip that one service exactly like the nil case —
so every other GraphQL failure (auth, rate-limit, schema drift) still
propagates fail-loud. Adds red-green coverage: a single thrown not-found
is skipped (healthy services still snapshot), while an unrelated GraphQL
error still raises.
… never probed

When a promote fails, the succeeded-service set is empty, so verify-prod
hits its skip branch (`exit 0`). The GitHub job result is therefore
`success`, and the notify step rendered `verify-prod=success` in the
#oss-alerts Slack message — a misleading green, since prod was never
probed.

verify-prod now exports a `status` output: `success` after a real probe
passes, `skipped` on the empty-CSV skip. notify reads that output (via
the new bats-tested verify-prod-display.sh) instead of the raw job
result, so the Slack line accurately reads `verify-prod=skipped` vs
`success` vs `failure`. A genuine probe failure / contract violation
exits non-zero (job result `failure`, status never written), and the
display falls back to the job result. Slack formatting is unchanged.

Extracts the display mapping into showcase/scripts/verify-prod-display.sh
(mirroring promote-fleet.sh) with red-green bats coverage, and adds it to
the showcase_validate.yml shellcheck step.
@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat-with-your-data Ready Ready Preview, Comment Jun 8, 2026 5:11pm
docs Ready Ready Preview, Comment Jun 8, 2026 5:11pm
form-filling Ready Ready Preview, Comment Jun 8, 2026 5:11pm
research-canvas Ready Ready Preview, Comment Jun 8, 2026 5:11pm
travel Ready Ready Preview, Comment Jun 8, 2026 5:11pm

Request Review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

📣 Social Copy Generator

Generate social media copies (Twitter/X, LinkedIn, Blog Post) for this PR using Claude.

  • Generate social media copies

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Production Digest-Pinning Audit

All 28 services digest-pinned.

Run 2026-06-08 10:09:06 PDT — 0 finding(s).

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 1 MB

ℹ️ View Unchanged
Filename Size
packages/a2ui-renderer/dist/_virtual/_rolldown/runtime.cjs 516 B
packages/a2ui-renderer/dist/a2ui-types.cjs 172 B
packages/a2ui-renderer/dist/a2ui-types.mjs 171 B
packages/a2ui-renderer/dist/index.cjs 842 B
packages/a2ui-renderer/dist/index.mjs 563 B
packages/a2ui-renderer/dist/index.umd.js 12.2 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/A2uiSurface.cjs 1.49 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/A2uiSurface.mjs 1.43 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/adapter.cjs 1.07 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/adapter.mjs 1.01 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/AudioPlayer.cjs 904 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/AudioPlayer.mjs 492 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Button.cjs 1 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Button.mjs 596 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Card.cjs 829 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Card.mjs 419 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/CheckBox.cjs 1.1 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/CheckBox.mjs 1.02 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/ChildList.cjs 799 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/ChildList.mjs 737 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/ChoicePicker.cjs 1.63 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/ChoicePicker.mjs 1.57 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Column.cjs 856 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Column.mjs 802 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/DateTimeInput.cjs 1.12 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/DateTimeInput.mjs 1.05 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Divider.cjs 842 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Divider.mjs 432 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Icon.cjs 877 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Icon.mjs 469 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Image.cjs 995 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Image.mjs 587 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/List.cjs 920 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/List.mjs 508 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Modal.cjs 1.19 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Modal.mjs 1.13 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Row.cjs 855 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Row.mjs 801 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Slider.cjs 1.06 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Slider.mjs 984 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Tabs.cjs 1.1 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Tabs.mjs 1.04 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Text.cjs 903 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Text.mjs 489 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/TextField.cjs 1.21 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/TextField.mjs 1.14 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Video.cjs 788 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/components/Video.mjs 378 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/index.cjs 928 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/index.mjs 855 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/utils.cjs 579 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/basic/utils.mjs 556 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Button.cjs 982 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Button.mjs 580 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/ChildList.cjs 800 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/ChildList.mjs 735 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Column.cjs 1.08 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Column.mjs 1.02 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Row.cjs 1.07 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Row.mjs 1.01 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Text.cjs 894 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/Text.mjs 839 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/TextField.cjs 1.21 kB
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/components/TextField.mjs 802 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/index.cjs 849 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/catalog/minimal/index.mjs 804 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/index.cjs 310 B
packages/a2ui-renderer/dist/react-renderer/a2ui-react/index.mjs 325 B
packages/a2ui-renderer/dist/react-renderer/catalog-utils.cjs 1.59 kB
packages/a2ui-renderer/dist/react-renderer/catalog-utils.mjs 1.52 kB
packages/a2ui-renderer/dist/react-renderer/core/A2UIProvider.cjs 1.5 kB
packages/a2ui-renderer/dist/react-renderer/core/A2UIProvider.mjs 1.42 kB
packages/a2ui-renderer/dist/react-renderer/core/A2UIRenderer.cjs 728 B
packages/a2ui-renderer/dist/react-renderer/core/A2UIRenderer.mjs 671 B
packages/a2ui-renderer/dist/react-renderer/create-catalog.cjs 1.35 kB
packages/a2ui-renderer/dist/react-renderer/create-catalog.mjs 1.28 kB
packages/a2ui-renderer/dist/react-renderer/hooks/useA2UI.cjs 316 B
packages/a2ui-renderer/dist/react-renderer/hooks/useA2UI.mjs 309 B
packages/a2ui-renderer/dist/react-renderer/index.cjs 399 B
packages/a2ui-renderer/dist/react-renderer/index.mjs 515 B
packages/a2ui-renderer/dist/react-renderer/lib/utils.cjs 236 B
packages/a2ui-renderer/dist/react-renderer/lib/utils.mjs 188 B
packages/a2ui-renderer/dist/react-renderer/styles/index.cjs 239 B
packages/a2ui-renderer/dist/react-renderer/styles/index.mjs 231 B
packages/a2ui-renderer/dist/react-renderer/theme/ThemeContext.cjs 436 B
packages/a2ui-renderer/dist/react-renderer/theme/ThemeContext.mjs 381 B
packages/core/dist/index.cjs 31.9 kB
packages/core/dist/index.mjs 31.7 kB
packages/core/dist/index.umd.js 34.8 kB
packages/react-core/dist/copilotkit-CxNqECaR.cjs 97.2 kB
packages/react-core/dist/copilotkit-dKT9RIRY.mjs 95.9 kB
packages/react-core/dist/index.cjs 17 kB
packages/react-core/dist/index.mjs 16.9 kB
packages/react-core/dist/index.umd.js 63.7 kB
packages/react-core/dist/v2/context.cjs 1.53 kB
packages/react-core/dist/v2/context.mjs 1.46 kB
packages/react-core/dist/v2/headless.cjs 10.3 kB
packages/react-core/dist/v2/headless.mjs 9.73 kB
packages/react-core/dist/v2/index.cjs 1.16 kB
packages/react-core/dist/v2/index.mjs 998 B
packages/react-core/dist/v2/index.umd.js 97 kB
packages/react-textarea/dist/index.cjs 18.1 kB
packages/react-textarea/dist/index.mjs 17.4 kB
packages/react-textarea/dist/index.umd.js 18.8 kB
packages/react-ui/dist/index.cjs 24.8 kB
packages/react-ui/dist/index.mjs 24.1 kB
packages/react-ui/dist/index.umd.js 26 kB
packages/runtime-client-gql/dist/_virtual/_rolldown/runtime.cjs 516 B
packages/runtime-client-gql/dist/client/conversion.cjs 1.52 kB
packages/runtime-client-gql/dist/client/conversion.mjs 1.45 kB
packages/runtime-client-gql/dist/client/CopilotRuntimeClient.cjs 1.48 kB
packages/runtime-client-gql/dist/client/CopilotRuntimeClient.mjs 1.44 kB
packages/runtime-client-gql/dist/client/types.cjs 701 B
packages/runtime-client-gql/dist/client/types.mjs 642 B
packages/runtime-client-gql/dist/graphql/@generated/gql.cjs 850 B
packages/runtime-client-gql/dist/graphql/@generated/gql.mjs 850 B
packages/runtime-client-gql/dist/graphql/@generated/graphql.cjs 2.4 kB
packages/runtime-client-gql/dist/graphql/@generated/graphql.mjs 2.36 kB
packages/runtime-client-gql/dist/graphql/definitions/mutations.cjs 740 B
packages/runtime-client-gql/dist/graphql/definitions/mutations.mjs 732 B
packages/runtime-client-gql/dist/graphql/definitions/queries.cjs 316 B
packages/runtime-client-gql/dist/graphql/definitions/queries.mjs 304 B
packages/runtime-client-gql/dist/index.cjs 679 B
packages/runtime-client-gql/dist/index.mjs 495 B
packages/runtime-client-gql/dist/index.umd.js 9.46 kB
packages/runtime-client-gql/dist/message-conversion/agui-to-gql.cjs 1.7 kB
packages/runtime-client-gql/dist/message-conversion/agui-to-gql.mjs 1.67 kB
packages/runtime-client-gql/dist/message-conversion/gql-to-agui.cjs 1.56 kB
packages/runtime-client-gql/dist/message-conversion/gql-to-agui.mjs 1.53 kB
packages/runtime-client-gql/dist/package.cjs 181 B
packages/runtime-client-gql/dist/package.mjs 116 B
packages/shared/dist/_virtual/_rolldown/runtime.cjs 516 B
packages/shared/dist/a2ui-prompts.cjs 2.85 kB
packages/shared/dist/a2ui-prompts.mjs 2.85 kB
packages/shared/dist/attachments/utils.cjs 1.74 kB
packages/shared/dist/attachments/utils.mjs 1.7 kB
packages/shared/dist/constants/index.cjs 362 B
packages/shared/dist/constants/index.mjs 345 B
packages/shared/dist/debug.cjs 454 B
packages/shared/dist/debug.mjs 452 B
packages/shared/dist/finalize-events.cjs 1.09 kB
packages/shared/dist/finalize-events.mjs 1.05 kB
packages/shared/dist/index.cjs 1.76 kB
packages/shared/dist/index.mjs 1.46 kB
packages/shared/dist/index.umd.js 20.2 kB
packages/shared/dist/logger.cjs 112 B
packages/shared/dist/logger.mjs 112 B
packages/shared/dist/package.cjs 181 B
packages/shared/dist/package.mjs 116 B
packages/shared/dist/standard-schema.cjs 740 B
packages/shared/dist/standard-schema.mjs 737 B
packages/shared/dist/telemetry/lambda-client.cjs 1.15 kB
packages/shared/dist/telemetry/lambda-client.mjs 1.14 kB
packages/shared/dist/telemetry/telemetry-client.cjs 1.33 kB
packages/shared/dist/telemetry/telemetry-client.mjs 1.28 kB
packages/shared/dist/telemetry/utils.cjs 321 B
packages/shared/dist/telemetry/utils.mjs 277 B
packages/shared/dist/transcription-errors.cjs 951 B
packages/shared/dist/transcription-errors.mjs 944 B
packages/shared/dist/utils/clipboard.cjs 407 B
packages/shared/dist/utils/clipboard.mjs 405 B
packages/shared/dist/utils/conditions.cjs 540 B
packages/shared/dist/utils/conditions.mjs 536 B
packages/shared/dist/utils/console-styling.cjs 1.25 kB
packages/shared/dist/utils/console-styling.mjs 1.22 kB
packages/shared/dist/utils/errors.cjs 4.51 kB
packages/shared/dist/utils/errors.mjs 4.41 kB
packages/shared/dist/utils/index.cjs 1.39 kB
packages/shared/dist/utils/index.mjs 1.65 kB
packages/shared/dist/utils/json-schema.cjs 1.97 kB
packages/shared/dist/utils/json-schema.mjs 1.91 kB
packages/shared/dist/utils/random-id.cjs 618 B
packages/shared/dist/utils/random-id.mjs 559 B
packages/shared/dist/utils/requests.cjs 706 B
packages/shared/dist/utils/requests.mjs 704 B
packages/shared/dist/utils/types.cjs 161 B
packages/shared/dist/utils/types.mjs 160 B
packages/voice/dist/_virtual/_rolldown/runtime.cjs 516 B
packages/voice/dist/index.cjs 175 B
packages/voice/dist/index.mjs 95 B
packages/voice/dist/index.umd.js 1.5 kB
packages/voice/dist/transcription/transcription-service-openai.cjs 495 B
packages/voice/dist/transcription/transcription-service-openai.mjs 434 B
packages/web-inspector/dist/assets/inspector-logo-icon.cjs 499 B
packages/web-inspector/dist/assets/inspector-logo-icon.mjs 434 B
packages/web-inspector/dist/assets/inspector-logo.cjs 4.32 kB
packages/web-inspector/dist/assets/inspector-logo.mjs 4.23 kB
packages/web-inspector/dist/index.cjs 43.3 kB
packages/web-inspector/dist/index.mjs 43.2 kB
packages/web-inspector/dist/index.umd.js 59.6 kB
packages/web-inspector/dist/lib/context-helpers.cjs 862 B
packages/web-inspector/dist/lib/context-helpers.mjs 830 B
packages/web-inspector/dist/lib/persistence.cjs 1.2 kB
packages/web-inspector/dist/lib/persistence.mjs 1.16 kB
packages/web-inspector/dist/lib/telemetry.cjs 1.67 kB
packages/web-inspector/dist/lib/telemetry.mjs 1.63 kB
packages/web-inspector/dist/styles/generated.cjs 6.41 kB
packages/web-inspector/dist/styles/generated.mjs 6.35 kB

compressed-size-action

@jpr5
jpr5 merged commit c413ec3 into main Jun 8, 2026
37 checks passed
@jpr5
jpr5 deleted the fix/showcase-promote-reliability branch June 8, 2026 17:45
jpr5 added a commit that referenced this pull request Jun 8, 2026
A single-service `promote <svc>` ran check_service_set_parity over the
FULL staging vs FULL prod fleet and REFUSEd whenever staging carried any
service prod lacks. The live staging fleet legitimately contains 13
staging-only services — harness-workers (SSOT-modeled) and 12 starter-*
demos — so an otherwise-clean single-service promote (e.g. docs) is
blocked with `REFUSE: services in staging not in prod`.

Scope the "staging not in prod" REFUSE to the promote TARGET when a
single-service promote is in effect (intersect the staging-only set with
[target]). The target-absent-from-prod footgun still REFUSEs (target is
in the intersection), the "prod not in staging" arm is unchanged, and
full-fleet promotes (no --service) retain full strictness.

Complements #5322.
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