fix(codegen/hetzner): make create-only required fields optional in global args (swamp-club #2642) #356

Merged
stack72 merged 10 commits from 2642 into main 2026-09-29 15:19:32 +00:00
Owner

Fixes swamp-club #2642.

Problem

Generated Hetzner models marked every field that the create (POST) request requires as required in GlobalArgsSchema. swamp checks that schema in full in two places:

  • swamp model create with any --global-arg;
  • swamp workflow validate, for steps that name a model type rather than a definition.

So a model set up for get, lookup or list was rejected unless callers passed placeholder create values. For example, @swamp/hetzner-cloud/servers with only name failed with server_type and image missing.

Method runs validate global args with .partial(), so the read methods themselves already worked. The real failures are the two commands above, the same as for Cloudflare (#2645).

Fix (Hetzner codegen only; mirrors the Cloudflare fix, PR 344)

  • Split in the pipeline (codegen/hetzner/pipeline.ts): the POST body's required list becomes createRequiredProperties, limited to the body's own properties (Object.hasOwn).
  • Global args: every resource field is .optional() in GlobalArgsSchema. The synthetic name stays required.
  • Create check: the generated create throws create requires global arguments: <names> (sorted) before any API call, including the token check. undefined, null and "" count as missing.
  • Naming field: where the naming field is a real field that create requires (for example servers.name), nothing is ever stored under the current fallback, because create requires it and get/list/lookup/adopt store under the real name. So update, sync and the action methods throw <method> requires global argument: <field> when it is unset, instead of a misleading No data found. Resources whose naming field create doesn't require (floating_ips) keep the current fallback.
  • No PUT fill: Hetzner's PUT is a partial update (confirmed in #349), so update is unchanged, unlike Cloudflare.
  • Design doc (codegen/designs/hetzner.md): documents the rule and its trade-offs, and removes the incorrect claim that swamp does not enforce required global arguments.

Models

  • 11 models change: certificates, firewalls, floating_ips, load_balancers, networks, placement_groups, primary_ips, servers, ssh_keys, volumes, zones. pricing (synthetic name) and the read-only models do not.
  • Each changed model gets a single CalVer bump (2026.09.29.1) and an identity upgrade entry; manifest.yaml bumps once. The later regeneration commits start from main's model state, so they add only the check lines, with no extra version bumps.
  • A second generate:hetzner run produces no changes. The schema had no drift, so there is no separate baseline commit.

Behaviour changes to be aware of

  • swamp workflow validate and swamp model create no longer catch a definition meant for create that is missing a create-required field. The error appears when create runs, still before any API call.
  • swamp model type describe no longer marks those fields as required.
  • update, sync and actions on a definition without a create-required naming field fail with <method> requires global argument: <field>.

Testing

  • Generator tests: fields are emitted optional and the synthetic name stays required; the create check is sorted, runs before the API call, and is omitted when nothing is required; the naming-field check is emitted for servers in update/sync and not for an optional-name resource or a synthetic one. Snapshots updated; the snapshot diff is only these lines.
  • codegen/hetzner/integration_test.ts (new, Deno.serve mock with a fetch redirect):
    • a schema with only name and token parses;
    • create missing fields, or with an empty field, fails with zero requests (including the /locations token check);
    • update without name fails with the new message and no request;
    • get works with only the token, and a full create POSTs the fields.
      It fails against the pre-fix generator.
  • Codegen suite: 370 tests pass.
  • Manual, installed swamp binary:
    • model create for servers with only name succeeds (it failed on main);
    • a type-based workflow validate step with a get fails on main with Missing required inputs: server_type, image and passes on this branch;
    • create fails with create requires global arguments: image, server_type;
    • update without name fails with update requires global argument: name.
  • verify-build: 14/14 passed on 73d779bc7.
  • verify-reviews: code and adversarial reviews both passed, low findings only. The attestation is posted to swamp-club #2642 for this commit.

Changes after the plan's conformance review

Earlier verification rounds raised low review findings that were fixed on this branch: the create check treats null and "" as missing; Object.hasOwn in the pipeline filter; safer integration-test setup and teardown; the naming-field check; design-doc trade-off notes. The lifecycle can only return from verifying by recording a failed verification, so these are listed here rather than in the conformance record.

Known limitations (low review findings)

  • Only update runs the naming-field check in a test; sync and the four actions are covered by checks on the generated code. All six use the same generator helper.
  • Strings containing only spaces pass the create check; the API still rejects them before anything is created.
  • The upgrade entries say No schema changes although fields became optional. That text comes from the shared upgrade generator (codegen/shared/upgradesGenerator.ts), the same as for Cloudflare, and the identity upgrade is correct because the schema only got looser.

🤖 Generated with Claude Code

Fixes swamp-club #2642. ## Problem Generated Hetzner models marked every field that the create (POST) request requires as required in `GlobalArgsSchema`. swamp checks that schema in full in two places: - `swamp model create` with any `--global-arg`; - `swamp workflow validate`, for steps that name a model type rather than a definition. So a model set up for `get`, `lookup` or `list` was rejected unless callers passed placeholder create values. For example, `@swamp/hetzner-cloud/servers` with only `name` failed with `server_type` and `image` missing. Method runs validate global args with `.partial()`, so the read methods themselves already worked. The real failures are the two commands above, the same as for Cloudflare (#2645). ## Fix (Hetzner codegen only; mirrors the Cloudflare fix, PR 344) - **Split in the pipeline** (`codegen/hetzner/pipeline.ts`): the POST body's required list becomes `createRequiredProperties`, limited to the body's own properties (`Object.hasOwn`). - **Global args:** every resource field is `.optional()` in `GlobalArgsSchema`. The synthetic `name` stays required. - **Create check:** the generated `create` throws `create requires global arguments: <names>` (sorted) before any API call, including the token check. `undefined`, `null` and `""` count as missing. - **Naming field:** where the naming field is a real field that create requires (for example `servers.name`), nothing is ever stored under the `current` fallback, because create requires it and `get`/`list`/`lookup`/`adopt` store under the real name. So `update`, `sync` and the action methods throw `<method> requires global argument: <field>` when it is unset, instead of a misleading `No data found`. Resources whose naming field create doesn't require (`floating_ips`) keep the `current` fallback. - **No PUT fill:** Hetzner's PUT is a partial update (confirmed in #349), so `update` is unchanged, unlike Cloudflare. - **Design doc** (`codegen/designs/hetzner.md`): documents the rule and its trade-offs, and removes the incorrect claim that swamp does not enforce required global arguments. ## Models - 11 models change: certificates, firewalls, floating_ips, load_balancers, networks, placement_groups, primary_ips, servers, ssh_keys, volumes, zones. `pricing` (synthetic name) and the read-only models do not. - Each changed model gets a single CalVer bump (`2026.09.29.1`) and an identity upgrade entry; `manifest.yaml` bumps once. The later regeneration commits start from main's model state, so they add only the check lines, with no extra version bumps. - A second `generate:hetzner` run produces no changes. The schema had no drift, so there is no separate baseline commit. ## Behaviour changes to be aware of - `swamp workflow validate` and `swamp model create` no longer catch a definition meant for `create` that is missing a create-required field. The error appears when `create` runs, still before any API call. - `swamp model type describe` no longer marks those fields as required. - `update`, `sync` and actions on a definition without a create-required naming field fail with `<method> requires global argument: <field>`. ## Testing - **Generator tests:** fields are emitted optional and the synthetic name stays required; the create check is sorted, runs before the API call, and is omitted when nothing is required; the naming-field check is emitted for `servers` in `update`/`sync` and not for an optional-name resource or a synthetic one. Snapshots updated; the snapshot diff is only these lines. - **`codegen/hetzner/integration_test.ts`** (new, `Deno.serve` mock with a fetch redirect): - a schema with only `name` and `token` parses; - `create` missing fields, or with an empty field, fails with zero requests (including the `/locations` token check); - `update` without `name` fails with the new message and no request; - `get` works with only the token, and a full `create` POSTs the fields. It fails against the pre-fix generator. - **Codegen suite:** 370 tests pass. - **Manual, installed swamp binary:** - `model create` for servers with only `name` succeeds (it failed on main); - a type-based `workflow validate` step with a `get` fails on main with `Missing required inputs: server_type, image` and passes on this branch; - `create` fails with `create requires global arguments: image, server_type`; - `update` without `name` fails with `update requires global argument: name`. - **verify-build:** 14/14 passed on `73d779bc7`. - **verify-reviews:** code and adversarial reviews both passed, low findings only. The attestation is posted to swamp-club #2642 for this commit. ## Changes after the plan's conformance review Earlier verification rounds raised low review findings that were fixed on this branch: the create check treats `null` and `""` as missing; `Object.hasOwn` in the pipeline filter; safer integration-test setup and teardown; the naming-field check; design-doc trade-off notes. The lifecycle can only return from `verifying` by recording a failed verification, so these are listed here rather than in the conformance record. ## Known limitations (low review findings) - Only `update` runs the naming-field check in a test; `sync` and the four actions are covered by checks on the generated code. All six use the same generator helper. - Strings containing only spaces pass the create check; the API still rejects them before anything is created. - The upgrade entries say `No schema changes` although fields became optional. That text comes from the shared upgrade generator (`codegen/shared/upgradesGenerator.ts`), the same as for Cloudflare, and the identity upgrade is correct because the schema only got looser. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
swamp validates the full GlobalArgsSchema on `model create` with any
--global-arg and on type-based `workflow validate`, so the POST body's
required fields blocked definitions meant for get/lookup/list. Every
resource field is now optional there, the synthetic name stays required,
and create throws "create requires global arguments: <names>" before any
API call. Hetzner's PUT is partial, so update needs no fill.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerated with the updated codegen. 11 models change, each with one
CalVer bump and a no-op upgrade entry; a second generation run produces
no changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replaces the incorrect claim that swamp does not enforce required global
arguments: it validates the full schema on model create and type-based
workflow validate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The create check also treats null and empty-string values as missing,
  so they fail with the check message instead of an API error.
- The integration test drops the unneeded sanitizeOps: false and keeps a
  Request's method, headers and body when redirecting fetch.
- The design doc notes that missing create fields now surface when
  create runs rather than at model create or workflow validate time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only the create check lines change. Versions and upgrade entries are the
same as the previous regeneration, and a second generation run produces no
changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- createRequiredProperties filters with Object.hasOwn, so an inherited key
  such as toString can never pass as a body property.
- The integration test sets up fetch, env and the model inside try, and
  restores fetch and env before removing the temp dir.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the naming field is a real field that create requires, nothing is
stored under the "current" fallback, so update, sync and the action methods
now throw "<method> requires global argument: <field>" instead of failing
with a misleading "No data found". Resources whose naming field create does
not require keep the fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(hetzner): regenerate models with the naming-field check (swamp-club #2642)
All checks were successful
CI / Review Integrity (pull_request) Successful in 2m15s
CI / Validate Attestation (pull_request) Successful in 2m17s
73d779bc73
Adds the update/sync/action naming-field check to the 10 models whose
naming field create requires. Versions and upgrade entries are unchanged
from the previous regeneration, and a second run produces no changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
stack72 deleted branch 2642 2026-09-29 15:19:57 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
swamp-club/swamp-extensions!356
No description provided.