Skip to content

feat(errors): tighten error taxonomy + surface code/requestId#13

Merged
caballeto merged 4 commits intomainfrom
chore/codegen-audit-phase-2
Apr 22, 2026
Merged

feat(errors): tighten error taxonomy + surface code/requestId#13
caballeto merged 4 commits intomainfrom
chore/codegen-audit-phase-2

Conversation

@caballeto
Copy link
Copy Markdown
Contributor

Summary

Replaces the ad-hoc AuthError / ValidationError / NotFoundError / ApiRequestError set with a P4-aligned hierarchy:

```
DevhelmError (umbrella, exit 1)
├── DevhelmValidationError (exit 4)
├── DevhelmApiError (exit 11) — status, code, requestId
│ ├── DevhelmAuthError (401/403)
│ └── DevhelmNotFoundError (404)
└── DevhelmTransportError (exit 12)
```

  • Pinned EXIT_CODES: VALIDATION=4, CHANGES_PENDING=10, API=11, TRANSPORT=12, PARTIAL_FAILURE=13.
  • checkedFetch maps HTTP status → typed subclass and extracts code + requestId (header wins over body) onto every thrown error.
  • response-validation throws DevhelmValidationError (not bare ZodError) so exit-code routing is uniform regardless of which side fails.
  • Regenerated schemas + flag descriptions.

Test plan

  • pnpm test (856 passed)
  • mono surface tests (tests/surfaces/cli/, tests/surfaces/evolution/surfaces/test_cli.py) green

Made with Cursor

- Replace ad-hoc AuthError/ValidationError/NotFoundError/ApiRequestError
  with a P4-aligned hierarchy:
    DevhelmError (umbrella, exit 1)
      ├── DevhelmValidationError      (exit 4)
      ├── DevhelmApiError              (exit 11) — status, code, requestId
      │     ├── DevhelmAuthError       (401/403)
      │     └── DevhelmNotFoundError   (404)
      └── DevhelmTransportError       (exit 12)
- Pin EXIT_CODES contract: VALIDATION=4, CHANGES_PENDING=10, API=11,
  TRANSPORT=12, PARTIAL_FAILURE=13
- checkedFetch maps HTTP status → typed subclass and extracts code +
  requestId (header wins over body) onto every thrown error
- response-validation throws DevhelmValidationError (not bare ZodError)
  so exit-code routing is uniform regardless of failure side
- Regenerated schemas + descriptions pull through ErrorResponse with
  code+requestId

Made-with: Cursor
Three related YAML-apply fixes uncovered while auditing the codegen
pipelines for stray typecasts and silent drift:

1. Status page reorder was sending `groupId: null` for every component
   in the batch. The API treats the reorder payload as a full upsert
   (sets groupId to whatever's provided), so any deploy that triggered
   a reorder silently moved every grouped component back to "ungrouped".
   Fix: extend `applyReorder` to receive the desired YAML alongside the
   ordered IDs, and resolve each component's groupId from its YAML
   `group` reference. Also covered by a new BDD assertion in
   `mono/tests/surfaces/cli/bdd/test_b_org_structure.py`.

2. `checkedFetch` returned `unknown`, which forced every YAML handler
   to launder the response through a `castEnvelope<T>(...)` typecast.
   That defeats the spec-driven types from `openapi-fetch` and would
   hide spec drift at runtime. Make `checkedFetch<TData>` generic so
   the response keeps its inferred shape, drop `castEnvelope`, and
   route ID extraction through a small `idToString` helper.

3. Zod validation errors used dotted paths (`monitors.0.config.url`)
   while the handwritten validator used bracket notation
   (`monitors[0].config.url`), so users saw two different shapes for
   the same conceptual field depending on which validator caught the
   issue. Add a `formatZodPath` helper that always renders array
   indices as `[N]` and matches the validator's style, with unit tests.

Made-with: Cursor
…mands

- Replace ad-hoc exit codes (1, 2) with canonical EXIT_CODES taxonomy:
  - VALIDATION (4) for local config/state/parse failures
  - API (11) for auth/token failures and lock conflicts
  - GENERAL (1) only for unclassified runtime/IO errors
- Remove `as {data?: ...}` typecasts after checkedFetch — rely on the
  generic return type from the openapi-fetch client.
- Branch on DevhelmApiError.status (instead of substring-matching the
  message) for 404 ("no lock") and 409 ("lock held") in deploy/* so
  errors with those words in the body don't get misclassified.
- validate.ts now uses EXIT_CODES.VALIDATION for both schema-parse and
  semantic errors, matching the unified DevhelmValidationError taxonomy.

Touches: validate, plan, init, import, deploy/{index,force-unlock},
auth/{login,token,context/use,context/delete}, state/{rm,show,pull},
monitors/{pause,resume}, incidents/resolve, dependencies/track,
alert-channels/test, webhooks/test.

Made-with: Cursor
…teEnvironmentRequest

Picks up the mono spec change (`CreateEnvironmentRequest.isDefault` is
now nullable; server defaults to false). Regenerates the OpenAPI types,
Zod schemas, and flag descriptions accordingly:

  - api.generated.ts:        `isDefault?: boolean | null`
  - api-zod.generated.ts:    `z.boolean().nullish()`
  - descriptions.generated.ts: refreshed flag help text

No imperative or YAML command logic changed — the field simply becomes
optional on `devhelm environments create` and in the YAML loader.

Made-with: Cursor
@caballeto caballeto merged commit ca2906b into main Apr 22, 2026
3 checks passed
@caballeto caballeto deleted the chore/codegen-audit-phase-2 branch April 22, 2026 08:26
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