Skip to content

fix(cloudflare): declare Container's network shape, and correct the documented example - #1763

Open
Cyberistic wants to merge 1 commit into
alchemy-run:mainfrom
Cyberistic:fix/container-network-shape
Open

Cyberistic wants to merge 1 commit into
alchemy-run:mainfrom
Cyberistic:fix/container-network-shape

Conversation

@Cyberistic

Copy link
Copy Markdown

Fixes #1761.

What

Two changes, both needed:

  1. The documented example is corrected — it used a key the API rejects.
  2. Network is declared instead of inheriting unknown, which is what turns the mistake into a compile error rather than a deploy-time rejection.

The doc example could not work

// before
network: { assignIpv4: "predefined", mode: "public" },
// configuration.network: unrecognized key: "assignipv4"

It also could not fail any earlier: the generated schema types the whole object as unknown, and alchemy inherited that:

// @distilled.cloud/cloudflare/services/containers.ts
network?: unknown | null;
network: S.optional(S.NullOr(S.Unknown)),

So copying the example type-checked cleanly and only failed at deploy. The same is true of a key typed from memory.

What measuring showed

I ran real deploys varying only the network block (throwaway stack, --stage probe, destroyed afterwards), and the rules are tighter than the docs suggest:

network Result
{ assignIpv4: "predefined" } unrecognized key: "assignipv4"
{ assign_ipv4: "predefined" } network.mode: "public" is not allowed for this account, only "private"
{ assign_ipv4: "predefined", mode: "private" } network.assign_ip: assigning an IP with network mode private is not allowed
{ assign_ipv4: "none", assign_ipv6: "none", mode: "private" } ✅ accepted

Two consequences the example did not convey, and which the type declaration and the docs notes now do:

  • assign_ipv4: "predefined" implies mode: "public". With no mode given, the API inferred public and rejected it on an account restricted to private — so predefined is a public-networking request spelled in a different field, not a neutral "give it an address".
  • assign_ipv4 and mode: "private" are mutually exclusive. Combined with the account restriction, a private-only account has no usable assign_ipv4 value at all and must set "none".

I have therefore replaced the example with the private shape (which deploys anywhere) rather than merely re-spelling the old one, and documented both rules on the type.

Network

Declared locally, for the same reason Constraints and Affinities already are in this file — the generated schema is opaque, so anything not declared here cannot be type-checked:

export type Constraints = { tier?: number };
export type Affinities = { colocation?: "datacenter" };
export type Network = {
  assign_ipv4?: "none" | "predefined";
  assign_ipv6?: "none" | "predefined";
  mode?: "private" | "public";
};

Tests

test/types/ContainerNetwork.ts, following the existing type-test convention (test/types/, checked by tsc -b). Each rule is a @ts-expect-error, which is a real check in both directions: if the type wrongly accepts, the directive itself becomes an error.

Verified both ways — against the inherited unknown the file reports 4 errors; with the declaration it compiles clean, and pnpm exec tsc -b is clean across the workspace.

Not done, deliberately

The type does not express the assign_ipv4 + mode: "private" exclusion, because Network is a single object type and that rule is a cross-field constraint. A union of accepted combinations could encode it, but that is a heavier change than a doc correction warrants and it would fight the optional-key ergonomics. Happy to do it if you would prefer the constraint enforced rather than documented.

Scope

Cloudflare.Container as declared for an image/context container (the .main/.image path); the effect-native .make() variant was not exercised.

…ocumented example

The doc example for `Cloudflare.Container` could not work: it used
`network: { assignIpv4: "predefined", mode: "public" }`, but the API takes
snake_case `assign_ipv4` and rejects the camelCase spelling with
`configuration.network: unrecognized key: "assignipv4"`.

It could not fail any earlier either. The generated schema types the whole
object as `unknown` (`network: S.optional(S.NullOr(S.Unknown))` in the distilled
containers service) and alchemy inherited that, so copying the example compiled
cleanly and only failed at deploy.

Two things follow from declaring the shape, and running real deploys showed the
rules are tighter than the example suggests:

- `assign_ipv4: "predefined"` is not a neutral "give it an address" — it requests
  public reachability, so the API infers `mode: "public"` when `mode` is omitted
  and rejects it on an account that only permits private.
- `assign_ipv4` and `mode: "private"` are mutually exclusive; combining them is
  refused with `assigning an IP with network mode private is not allowed`.

So the example now shows the private shape, which deploys anywhere, and the
notes state both rules. `Network` is declared locally as `Constraints` and
`Affinities` already are, which turns a misspelled key into a compile error.

Measured with a throwaway stack (`--stage probe`), varying only the `network`
block, destroyed afterwards:

  { assignIpv4: "predefined" }                        -> unrecognized key "assignipv4"
  { assign_ipv4: "predefined" }                       -> infers public; not allowed on a private-only account
  { assign_ipv4: "predefined", mode: "private" }      -> assigning an IP with network mode private is not allowed
  { assign_ipv4: "none", assign_ipv6: "none", mode: "private" } -> accepted

The type test asserts each rule; it reports 4 errors against the inherited
`unknown` and compiles clean with the declaration.
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.

bug(Cloudflare.Container): documented network example uses assignIpv4, which the API rejects (and the unknown typing hides it)

1 participant