Skip to content

Describe a compute provider in one place - #3946

Merged
rossnelson merged 1 commit into
feat/cloud-run-public-previewfrom
refactor/compute-provider-registry
Sep 28, 2026
Merged

rossnelson merged 1 commit into
feat/cloud-run-public-previewfrom
refactor/compute-provider-registry

Conversation

@rossnelson

@rossnelson rossnelson commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #3945. Base is feat/cloud-run-public-preview, so the diff here is the refactor alone. Merge #3945 first.

Description & motivation 💭

A compute provider was described in ten places, in four shapes, under two naming conventions:

Where Shape
compute-provider-picker — providerIcon Record
compute-provider-picker — providerLabel() switch
compute-provider-picker — providerDescription() switch
compute-provider-picker — defaultProviders array literal
shared.ts — defaultReleaseStage Record
compute-badge.svelte — CONFIG Record, keyed by wire name
lock-compute-provider.ts — providerValue if-chain over both namings
compute-fields.svelte — templates 5 ?raw imports + provider ternaries

Adding a provider meant finding all ten. The templates were the part that hurt most visibly: seven props (cfnTemplate, cfnTemplateUrl, terraformTemplate, cloudRunTerraformTemplate, agentCoreCfnTemplate, agentCoreCfnTemplateUrl, agentCoreTerraformTemplate) threaded through pages → forms → fields, growing by three with each new provider.

COMPUTE_PROVIDERS now holds all of it and the ten readers derive from it. Adding a provider is one entry.

The exhaustiveness is the point

The union derives from a tuple, not the other way round, and the maps are annotated rather than cast:

export const COMPUTE_PROVIDER_VALUES = ['lambda', 'agentcore', 'cloud-run'] as const;
export type ComputeProviderValue = (typeof COMPUTE_PROVIDER_VALUES)[number];

export const COMPUTE_PROVIDERS: Record<ComputeProviderValue, ComputeProviderDefinition> = { ... };

I verified this rather than assuming it. Adding a fourth provider to the tuple produces six compile errors naming it — in the registry, the release-stage map, and all three form schemas:

Property '"azure-functions"' is missing in type '{ lambda: ...; agentcore: ...; 'cloud-run': ... }'
  but required in type 'Record<"lambda" | "agentcore" | "cloud-run" | "azure-functions", ...>'

The same rule covers consumers. templates is one exhaustive map, so Temporal Cloud — which overrides them per environment — fails its own type check when a provider is added without templates, instead of silently falling back to the bundled defaults and showing a customer an infrastructure snippet pointing at the wrong account.

Design Considerations 🎨

  • Annotation, not satisfies. satisfies preserves literal types, which drops optional fields from the union — terraformModuleHref became unreachable because Cloud Run has none. The annotation gives exhaustiveness and uniform access.
  • i18n keys are typed as keys, not string. labelKey: I18nKey<I18nResources> means a typo is a compile error rather than a label rendering as workers.provider-lamda.
  • No casts on the provider. provider arrives as a form field, so it is narrowed through computeProviderFromType rather than asserted. An unrecognised value stays unrecognised instead of reading as a provider with no entry.
  • Two asymmetries are modelled, not inferred. CloudFormation is AWS-only, so it is optional on the templates type and Cloud Run simply has none — replacing the isAgentCore ternaries that decided it. The Terraform module link is per provider rather than an AWS-only string built from a boolean.
  • A ternary disappears entirely. interpolateTerraformTemplate already no-ops on whichever ARN key is absent from a snippet — its own comment said so — so both are passed.

What this does not touch

The {#if provider === 'lambda'} blocks in compute-fields.svelte markup. Those render genuinely different inputs and belong where they are.

Testing 🧪

How was this tested 👻

  • Behaviour-preserving; existing tests unchanged except where they passed the old props
  • Exhaustiveness verified by adding a provider and reading the errors
  • Verified against the real consumer

135 passed across the worker, deployment, and utility suites. count-strict-errors.ts reports 0.

Verified against a downstream consumer by building a package from this branch and type-checking it there: no new errors.

compute-fields.test.ts is the only test that changed shape — it passed two template props, and now layers an override onto the default map.

Checklists

Merge Checklist

Issue(s) closed

None — groundwork so the next provider is one entry rather than ten edits.

@rossnelson
rossnelson requested a review from a team as a code owner September 19, 2026 17:53
@vercel

vercel Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
holocene Ready Ready Preview Sep 19, 2026 10:39pm UTC

Request Review

A provider was described in ten places, in four shapes, under two naming
conventions: an icon map and two label switches in the picker, a release
stage map in shared.ts, a second icon-and-label map in compute-badge keyed
by the wire name rather than the short one, an if-chain normalising those
wire names in lock-compute-provider, a default provider list, and five raw
template imports resolved through provider ternaries in compute-fields.
Adding a provider meant finding all ten, and the templates alone had grown
to seven props threaded through pages, forms, and fields.

COMPUTE_PROVIDERS now holds all of it, keyed by provider, and the ten
readers derive from it. The Record is exhaustive over ComputeProviderValue,
so adding a provider is one entry and the compiler names whatever is still
missing.

The seven template props collapse to one `templates` map, defaulting to
the bundled templates that a self-hosted deployment wants. Because that
map is exhaustive too, a consumer supplying its own — Temporal Cloud does,
per environment — fails its type check when a provider is added without
templates, rather than silently falling back and shipping the wrong
infrastructure snippet.

Two asymmetries are now modelled rather than inferred. CloudFormation is
AWS-only, so it is optional on the templates type and Cloud Run simply has
none, in place of the isAgentCore ternaries that decided it. The Terraform
module link is likewise per provider rather than an AWS-only string built
from a boolean.

The provider ternary for Terraform interpolation is gone entirely:
interpolateTerraformTemplate already no-ops on the ARN key absent from a
given snippet, which its own comment noted, so both are passed.

Behaviour is unchanged. No markup branching was touched: the per-provider
field blocks render genuinely different inputs and belong where they are.
@rossnelson
rossnelson force-pushed the feat/cloud-run-public-preview branch from b6aea5a to 4dedc31 Compare September 19, 2026 22:38
@rossnelson
rossnelson force-pushed the refactor/compute-provider-registry branch from 4e0629e to 722376a Compare September 19, 2026 22:38
@rossnelson
rossnelson merged commit 0ad071e into feat/cloud-run-public-preview Sep 28, 2026
4 checks passed
@rossnelson
rossnelson deleted the refactor/compute-provider-registry branch September 28, 2026 16:10
rossnelson added a commit that referenced this pull request Sep 30, 2026
* feat(workers): move Cloud Run to public preview

Cloud Run has been behind a pre-release badge since it was added. It is
going to public preview, so the shared default release stage now says so,
putting it alongside Lambda.

This is the badge only. It does not change which providers a caller is
offered: self-hosted already offers all three, and a consumer that
restricts the list decides its own before handing it to the picker.

The badge also carries its stage as a data attribute now, so a test can
assert the stage rather than the words rendered for it. The stage is the
stable fact; the label is copy, and this change is itself an example of
that copy moving. The stories assert the attribute, including the two
that already read the rendered text.

Adds a story for the shape a consumer renders for a GCP namespace, with
the AWS providers hidden and no explicit releaseStage, so the assertion
reads the shared default rather than a caller override.

* refactor(workers): describe a compute provider in one place (#3946)

A provider was described in ten places, in four shapes, under two naming
conventions: an icon map and two label switches in the picker, a release
stage map in shared.ts, a second icon-and-label map in compute-badge keyed
by the wire name rather than the short one, an if-chain normalising those
wire names in lock-compute-provider, a default provider list, and five raw
template imports resolved through provider ternaries in compute-fields.
Adding a provider meant finding all ten, and the templates alone had grown
to seven props threaded through pages, forms, and fields.

COMPUTE_PROVIDERS now holds all of it, keyed by provider, and the ten
readers derive from it. The Record is exhaustive over ComputeProviderValue,
so adding a provider is one entry and the compiler names whatever is still
missing.

The seven template props collapse to one `templates` map, defaulting to
the bundled templates that a self-hosted deployment wants. Because that
map is exhaustive too, a consumer supplying its own — Temporal Cloud does,
per environment — fails its type check when a provider is added without
templates, rather than silently falling back and shipping the wrong
infrastructure snippet.

Two asymmetries are now modelled rather than inferred. CloudFormation is
AWS-only, so it is optional on the templates type and Cloud Run simply has
none, in place of the isAgentCore ternaries that decided it. The Terraform
module link is likewise per provider rather than an AWS-only string built
from a boolean.

The provider ternary for Terraform interpolation is gone entirely:
interpolateTerraformTemplate already no-ops on the ARN key absent from a
given snippet, which its own comment noted, so both are passed.

Behaviour is unchanged. No markup branching was touched: the per-provider
field blocks render genuinely different inputs and belong where they are.

This branch was successfully deployed

1 active deployment
Preview — 722376a5 Deployed Sep 19, 2026 by vercel[bot]
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