fix(codegen/vercel): deployments sync works after adopt, get and create; lookup filters on list item fields (swamp-club #2843) #450

Merged
stack72 merged 2 commits from 2843 into main 2026-10-02 22:24:13 +00:00
Owner

Fixes swamp-club #2843.

Problem

sync on @swamp/vercel/deployments/deployments always failed with Stored state has no uid - cannot sync for state written by adopt, get or create. Those calls read /v13/deployments, which returns the deployment's id; only the /v7/deployments list used by lookup returns uid. The Vercel pipeline mapped the {idOrUrl} path param to uid.

lookup with project: prj_xxx also never matched, because list items expose projectId, not project.

Fix (codegen/vercel, regenerated)

  • Identifier: idOrUrl now maps to id. The pipeline records the list-item identifier separately (listIdentifyingField, uid for deployments). When the two differ, the generated sync and update use existing.id ?? existing.uid, so state written by create, get, adopt or lookup can all be synced. Models with a single identifier generate the same code as before.

  • Lookup filters: when the list item schema is known, lookup filters only on fields items carry. project is compared against projectId, and the read id against the list uid. Create-only arguments that items never carry stop being filters, since a set value made every lookup fail. When the item schema can't be identified, all filters stay. A remapped filter's no-match error names the argument that was set: project="my-app" (matched against projectId).

  • Sensitive fields: top-level request-body fields are emitted with .meta({ sensitive: true }) and never used as lookup filters if any of these hold:

    • the spec marks them writeOnly or format: password;
    • they are strings whose names end in secret, password, token, credential(s), privatekey or apikey (the tailscale rule);
    • they are listed explicitly (importKey, a KMS PEM private key).

    This marks deployments.gitAccessToken and kms issuers.importKey.

Regenerated services, each with one CalVer bump:

  • deployments: the identifier fix, lookup filters, and sensitive gitAccessToken.
  • kms: sensitive importKey, and drops lookup filters list items lack.
  • domains, environment, feature-flags, networking, edge-config (items): drop lookup filters list items lack.
  • edge-config (global_config): carries upstream spec drift. It is kept so model/ matches generator output.

Design doc updated: codegen/designs/vercel.md section 8.

⚠️ Breaking change: gitAccessToken / importKey must come from a vault

swamp core refuses literal values for sensitive arguments. A definition that sets gitAccessToken (deployments) or importKey (kms issuers) as a plain value fails every method after upgrading:

Error: Global argument 'gitAccessToken' is marked sensitive and cannot be set to a literal value ...

To migrate, find the affected definitions, store the value in a vault, and reference it:

swamp doctor secrets
swamp vault put <vault> gitAccessToken <value>
# definition: gitAccessToken: ${{ vault.get('<vault>', 'gitAccessToken') }}

Previously the plain token sat in the definition YAML, was written into every run's method-summary report, and was printed in lookup's no-match error. All three were confirmed against the published 2026.10.01.1.

Testing

  • Unit tests: pipeline tests cover identifier mapping, list-item extraction and sensitive detection. Generator tests cover the fallback, filter remapping and sensitive meta.

  • Integration test: codegen/vercel/deployments_identifier_integration_test.ts uses a mock server. It covers adopt/create/lookup → sync, a not_found sync, and that the no-match error omits the secret. It fails on the old keying with the issue's exact error.

  • End to end: run on the vercel-labs/emulate Vercel emulator in a temporary swamp repo, with the branch added via swamp extension source add:

    Scenario 2026.10.01.1 This branch
    create → sync pass* pass
    adopt → sync pass* pass
    lookup by project (same name in two projects) → sync fails pass, correct deployment
    plain gitAccessToken in definition leaks the token refused (breaking change above)
    vault-wired gitAccessToken: lookup → sync, create — pass, token never on disk

    * The emulator returns both id and uid from get/create, unlike real Vercel, so it cannot reproduce the original sync failure; the unit and integration tests cover that.

  • Regeneration: idempotent (the second run produces no diff). The full codegen suite passes, 434 tests.

Known gaps

  • Nested secrets are not marked sensitive, notably drains delivery.secret. This is documented in the design doc.
  • Lookup's filter rule trusts the spec's list item schema. If real list items carry a field the spec omits, a filter on it is now ignored, which widens the match. Lookup still requires exactly one match.

Verified on 1f0df5241 (verify-build 14/14, verify-reviews pass); attestation posted.

🤖 Generated with Claude Code

Fixes swamp-club #2843. ## Problem `sync` on `@swamp/vercel/deployments/deployments` always failed with `Stored state has no uid - cannot sync` for state written by `adopt`, `get` or `create`. Those calls read `/v13/deployments`, which returns the deployment's `id`; only the `/v7/deployments` list used by `lookup` returns `uid`. The Vercel pipeline mapped the `{idOrUrl}` path param to `uid`. `lookup` with `project: prj_xxx` also never matched, because list items expose `projectId`, not `project`. ## Fix (codegen/vercel, regenerated) - **Identifier:** `idOrUrl` now maps to `id`. The pipeline records the list-item identifier separately (`listIdentifyingField`, `uid` for deployments). When the two differ, the generated `sync` and `update` use `existing.id ?? existing.uid`, so state written by `create`, `get`, `adopt` or `lookup` can all be synced. Models with a single identifier generate the same code as before. - **Lookup filters:** when the list item schema is known, `lookup` filters only on fields items carry. `project` is compared against `projectId`, and the read `id` against the list `uid`. Create-only arguments that items never carry stop being filters, since a set value made every lookup fail. When the item schema can't be identified, all filters stay. A remapped filter's no-match error names the argument that was set: `project="my-app" (matched against projectId)`. - **Sensitive fields:** top-level request-body fields are emitted with `.meta({ sensitive: true })` and never used as lookup filters if any of these hold: - the spec marks them `writeOnly` or `format: password`; - they are strings whose names end in secret, password, token, credential(s), privatekey or apikey (the tailscale rule); - they are listed explicitly (`importKey`, a KMS PEM private key). This marks `deployments.gitAccessToken` and `kms issuers.importKey`. Regenerated services, each with one CalVer bump: - **deployments:** the identifier fix, lookup filters, and sensitive `gitAccessToken`. - **kms:** sensitive `importKey`, and drops lookup filters list items lack. - **domains, environment, feature-flags, networking, edge-config (items):** drop lookup filters list items lack. - **edge-config (global_config):** carries upstream spec drift. It is kept so `model/` matches generator output. Design doc updated: `codegen/designs/vercel.md` section 8. ## ⚠️ Breaking change: gitAccessToken / importKey must come from a vault swamp core refuses literal values for sensitive arguments. A definition that sets `gitAccessToken` (deployments) or `importKey` (kms issuers) as a plain value fails every method after upgrading: ``` Error: Global argument 'gitAccessToken' is marked sensitive and cannot be set to a literal value ... ``` To migrate, find the affected definitions, store the value in a vault, and reference it: ``` swamp doctor secrets swamp vault put <vault> gitAccessToken <value> # definition: gitAccessToken: ${{ vault.get('<vault>', 'gitAccessToken') }} ``` Previously the plain token sat in the definition YAML, was written into every run's method-summary report, and was printed in lookup's no-match error. All three were confirmed against the published 2026.10.01.1. ## Testing - **Unit tests:** pipeline tests cover identifier mapping, list-item extraction and sensitive detection. Generator tests cover the fallback, filter remapping and sensitive meta. - **Integration test:** `codegen/vercel/deployments_identifier_integration_test.ts` uses a mock server. It covers `adopt`/`create`/`lookup` → `sync`, a not_found `sync`, and that the no-match error omits the secret. It fails on the old keying with the issue's exact error. - **End to end:** run on the [vercel-labs/emulate](https://github.com/vercel-labs/emulate) Vercel emulator in a temporary swamp repo, with the branch added via `swamp extension source add`: | Scenario | 2026.10.01.1 | This branch | |---|---|---| | create → sync | pass* | pass | | adopt → sync | pass* | pass | | lookup by project (same name in two projects) → sync | fails | pass, correct deployment | | plain gitAccessToken in definition | leaks the token | refused (breaking change above) | | vault-wired gitAccessToken: lookup → sync, create | — | pass, token never on disk | \* The emulator returns both `id` and `uid` from get/create, unlike real Vercel, so it cannot reproduce the original sync failure; the unit and integration tests cover that. - **Regeneration:** idempotent (the second run produces no diff). The full codegen suite passes, 434 tests. ## Known gaps - Nested secrets are not marked sensitive, notably drains `delivery.secret`. This is documented in the design doc. - Lookup's filter rule trusts the spec's list item schema. If real list items carry a field the spec omits, a filter on it is now ignored, which widens the match. Lookup still requires exactly one match. Verified on 1f0df5241 (verify-build 14/14, verify-reviews pass); attestation posted. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Deployments sync keyed on uid, but get, adopt and create store the
deployment's id (only the v7 list used by lookup returns uid), so sync
always failed with "Stored state has no uid". The pipeline now maps the
idOrUrl path param to id and records the list-item identifier (uid)
separately; when the two differ, sync and update key on
existing.id ?? existing.uid, and lookup names instances by the list id.

Lookup now filters only on fields list items carry: project matches
projectId, the read id matches the list uid, and create-only args that
items never carry are no longer filters, so setting them no longer makes
every lookup fail. Item schemas the pipeline cannot identify keep today's
filters.

Top-level secret request-body fields (writeOnly, format password, a
secret-like name, or the KMS importKey private key) are emitted with
sensitive meta and never used as lookup filters, so a no-match error can
no longer print them. Nested secrets (drains delivery.secret) are a
documented known gap.

Regenerated: deployments, domains, edge-config, environment,
feature-flags, kms, networking. edge-config also carries upstream schema
drift in global_config, kept so model/ matches generator output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(codegen/vercel): name the set arg in remapped lookup errors; secret-name rule applies to strings only (swamp-club #2843)
All checks were successful
CI / Review Integrity (pull_request) Successful in 1m3s
CI / Validate Attestation (pull_request) Successful in 1m3s
1f0df5241e
Addresses the verify-reviews findings on the previous commit:

- A lookup filter matched against a differently named item field
  (project -> projectId, id -> uid) now reports the global arg the user
  set: project="my-app" (matched against projectId). project also accepts
  a project name, which never equals projectId, and the error used to name
  only projectId. Models without such a remap generate the same code.
- The secret-name rule only marks string fields sensitive, so a boolean
  like hasSecret stays an ordinary field and lookup filter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
stack72 deleted branch 2843 2026-10-02 22:24:14 +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!450
No description provided.