Skip to content

fix(schema): sync Upsun config schema with the Upsun CLI - #220

Merged
Theosakamg merged 7 commits into
masterfrom
fix/upsun-schema-from-cli
Oct 16, 2026
Merged

Theosakamg merged 7 commits into
masterfrom
fix/upsun-schema-from-cli

Conversation

@pjcdawkins

@pjcdawkins pjcdawkins commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Updates resources/validation/upsun.json, served at GET /schema/upsun, which had drifted from what the platform accepts.

The file is replaced with the schema maintained by the Upsun CLI for upsun lint (internal/lint/schema/upsun-config-schema.json), from upsun/cli#230, upsun/cli#234 and upsun/cli#242. Main changes:

  • stack is a mapping with runtimes and packages keys, or empty ([], {} or null). The legacy string and list forms are rejected. Each runtime is a string or a single-key mapping; each package is a string or a mapping with a package key.
  • Adds tasks, image, egress, authorizations, post_start, operation timeout/stop, the local mount source, dynamic_compression on locations and rules, and null TLS values.
  • Unknown keys are rejected, except those starting with . (YAML anchors and notes), which the platform ignores at the top level, in the applications, services and routes sections, and in every mapping with fixed keys except stack.
  • Explicit null is accepted on the optional fields where the platform treats it as unset (e.g. web.size, location passthru, operation timeout, egress port).
  • applications is no longer required, as files in .upsun/ are merged and a project may have none.
  • container_profile accepts any name, as profiles are per project; the standard ones are listed as examples.
  • request_buffering.max_request_size accepts an integer.
  • web.commands.start is no longer required.
  • web and workers accept the application keys the platform lets them override, except disk, which Upsun sets through the resources API (adds additional_hosts and operations), described as overrides and without defaults. egress.build is rejected on both: build-phase egress is only read from the application.
  • Each $ref with a sibling description is wrapped in allOf, since draft-07 ignores keywords beside $ref.
  • Removes the deprecated firewall key in favor of egress.
  • Removes defaults that fail their own schema (such as services.*.type: {}), which editors insert on completion, and a tab in the hooks.deploy description.

Changes on top of the CLI's copy:

  • Keeps the $refs to /schema/runtime-versions and /schema/service-versions for application, task and service types (the CLI checks types in code instead).
  • Also accepts composable:* and docker:* application and task types. Neither is in the runtime-versions enum, so every composable app was previously rejected.
  • Adds maxItems: 0 to stack, so a non-empty list such as stack: ["php@8.4"] fails. The CLI rejects this in code with a clearer message; in JSON Schema the error is "must NOT have more than 0 items".

As with the CLI's schema, some cross-field rules are not expressed here and are only checked by upsun lint, e.g. task base/run combinations and authorization action/resource by type.

🤖 Generated with Claude Code

pjcdawkins and others added 2 commits October 15, 2026 09:15
Replace resources/validation/upsun.json (served at /schema/upsun) with the
schema maintained by the Upsun CLI for `upsun lint`
(internal/lint/schema/upsun-config-schema.json), which has been corrected
against what the platform accepts. Notably, `stack` is now a mapping with
`runtimes` and `packages` keys, or empty.

Meta-specific changes on top of the CLI copy:
- Keep the $refs to /schema/runtime-versions and /schema/service-versions
  for application and service types.
- Also accept `composable:*` application types, which the runtime-versions
  enum does not include, so composable apps were always rejected.
- Add `maxItems: 0` to `stack`, so a non-empty (legacy) list is rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime-versions enum has no docker entries, so applications using a
`docker:` type with the `image` key were rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Changes suggested — 🟡 1 warning · 🔵 3 minor points

🔍 Full review · 1 file reviewed

Verification
  • I ran the merged schema through a draft-07 validator with the runtime and service refs stubbed: stack: [], {} and null pass, and stack: ["php@8.4"] and stack: "php" fail.
  • composable:25.05 with a runtimes/packages stack validates, and an unknown runtime type such as foo:1 is rejected by the anyOf on application type.
  • Top-level .x anchor keys pass and unknown top-level keys fail, as the new patternProperties / additionalProperties: false intend.
  • The served schema still points at https://meta.upsun.com/schema/runtime-versions and service-versions, and both endpoints return {type: string, enum} snippets that match those refs.

The diff adds no tests. The Validate Upsun config.yaml schema job (npm run validate:upsun) in validate-registry.yml only checks that the file is JSON with a loose zod shape. It does not compile the schema or validate any sample configs, so nothing checks the new semantics.

Review details
  • Commit: 6c52363
  • Model: claude-opus-5-5

Review 1 of 10 for this pull request · View the full run

Comment thread resources/validation/upsun.json
Comment thread resources/validation/upsun.json Outdated
Comment thread resources/validation/upsun.json Outdated
Comment thread resources/validation/upsun.json
- Fix the broken docs URL in the hooks.deploy description.
- Remove the object default on the string service type.
- Check task types against the runtime versions, like application types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Reviewed — No new issues found · 1 still open

🔁 Incremental · 1 file reviewed

Outstanding from earlier reviews:

  • 🟡 #4280670992 — resources/validation/upsun.json:334: Users get misplaced config that validates cleanly but does nothing (or fails at deploy), which defeats the point of the schema. — Not fixed: authorizations, egress and container_profile are still listed under applications.*.web.properties (lines 334–342), so the schema still accepts them as web keys.
Verification
  • tasks.*.type now uses the same runtime-versions $ref / ^(composable|docker): anyOf as applications.*.type at lines 12–20.
  • The hooks.deploy description URL now matches its build and post_deploy siblings, and no \t sequences remain anywhere in the file.
  • The service type no longer carries the object default: {}, and the file still parses as valid JSON.

Nothing in the diff tests these schema edits, and no test checks that upsun.json validates sample configs. I checked only that the file still parses as JSON.

Review details

Review 2 of 10 for this pull request · View the full run

@pjcdawkins
pjcdawkins marked this pull request as draft October 15, 2026 09:02
pjcdawkins and others added 2 commits October 15, 2026 10:11
Sync with upsun/cli#234:

- Accept additional_hosts, firewall and operations under web, and
  additional_hosts and operations under workers, as overrides of the
  application's values. Reject egress.build on workers.
- Describe override keys as overrides, without defaults.
- Remove defaults that fail their own schema.

The meta-specific type references and the stack maxItems are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sync with upsun/cli#234, which removes `firewall` (deprecated in
favor of `egress`) from applications, web, workers and services.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjcdawkins

Copy link
Copy Markdown
Contributor Author

I ran this schema against a set of configurations the platform's validator accepts, with Upsun settings (sizing API and unified configuration on). It rejects these, all of which validate on the platform:

Explicit null on optional fields. The validator treats null as unset; the schema types these without null:

applications:
  app:
    type: php:8.4
    web:
      size: null
      locations:
        /:
          passthru: null
    operations:
      op:
        timeout: null
        commands:
          start: echo
    egress:
      runtime:
        allowed_domains:
          - host: api.example.com
            port: null

.-prefixed keys inside objects. The schema allows them at the top level only, but the validator drops them at every level, so a YAML anchor block or a note inside web, a location, a mount, a cron or a route is rejected:

routes:
  "https://{default}/":
    type: upstream
    upstream: app:http
    .note: x

Other shapes:

  • request_buffering.max_request_size as an integer (5242880); the schema only allows a string.
  • dynamic_compression on a location rule.
  • A config file without applications, such as a .upsun/routes.yaml beside a .upsun/config.yaml. Files in .upsun/ are merged, so each file need not carry every section.
  • A container_profile outside the four listed. Profiles are per project, so an enum rejects custom ones.

The null handling and nested . keys are the most likely to show up in real configs.

Written by Claude Code.

@pjcdawkins
pjcdawkins marked this pull request as ready for review October 15, 2026 14:30

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Changes suggested — 🟡 1 warning · 🔵 1 minor point

🔁 Incremental (head + base moved) · 1 file reviewed

Verification
  • The web and worker property lists now reference the new shared additional_hosts and operations definitions, and both definitions resolve within definitions.
  • egress.build and egress.runtime both resolve to the new egress_phase definition, which has the same host/port shape as the inline objects it replaces.
  • The firewall key is removed from the app properties, the worker properties, the property-order list, the service properties and definitions, and no #/definitions/firewall reference is left.
  • The new max_request_size default "250m" matches that property's type: string; the old integer default did not.

The diff adds no tests. backend/src/scripts/validate-upsun.ts only checks that upsun.json is valid JSON and has the expected schema structure, so nothing tests that sample configs are accepted or rejected as intended.

Review details

Review 3 of 10 for this pull request · View the full run

Comment thread resources/validation/upsun.json Outdated
Comment thread resources/validation/upsun.json Outdated
- Build-phase egress is read from the application only, so a `build`
  allowlist under `web.egress` has no effect. Use the runtime-only egress
  definition (renamed `container_egress`) for web as well as workers.
- Draft-07 ignores keywords beside `$ref`, so the override descriptions
  were not shown. Move each `$ref` into an `allOf`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copies the CLI's schema, which now accepts configs the platform accepts:

- Explicit null on optional fields, where the platform treats it as unset.
- Keys starting with "." in mappings with fixed keys and in the
  applications, services and routes sections (YAML anchors and notes).
- An integer `request_buffering.max_request_size`, and
  `dynamic_compression` on locations and rules.
- Files without `applications`, as files in .upsun are merged.
- Any container profile name, as profiles are per project.

The web `egress.build` rejection and the `allOf` wrapping of `$ref`s are
now in the CLI's copy too.

The runtime and service version `$ref`s, the `composable:`/`docker:`
pattern and the stack `maxItems: 0` are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@upsun-dispatch

Copy link
Copy Markdown

📋 PR Summary

Replaces resources/validation/upsun.json, which is served at GET /schema/upsun, with the Upsun CLI's upsun lint schema plus a few local changes. The local changes keep the runtime-versions and service-versions $refs, accept composable:* and docker:* types, and reject build egress on web and workers. The latest push accepts explicit null on most optional fields and .-prefixed keys at every nesting level. It also allows integer max_request_size, adds dynamic_compression to locations and location rules, drops the required applications key, and lets container_profile take any project-defined profile instead of a fixed list.

Changes
Layer / File(s) Summary
Config schema
resources/validation/upsun.json Synced with the CLI's schema: adds tasks, image, egress, authorizations and operation timeouts, and now uses mapping-only stack. This push makes optional fields accept null and allows .-prefixed keys inside every object. It also adds dynamic_compression, accepts integer max_request_size, removes the required applications key, and replaces the container_profile enum with examples.

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Changes suggested — 🟡 1 warning

🔁 Incremental (head + base moved) · 1 file reviewed

Verification
  • Each new anyOf: [{$ref}, {type: null}] keeps its sibling description outside the $ref, so draft-07 still applies the description.
  • container_profile now accepts any string or null and keeps the four built-in profiles only as examples, so custom project profiles validate.
  • Every additionalProperties: false object except the stack definition now has the ^\. patternProperties escape; I checked this by walking the schema with a script.
  • Dropping the root required: [applications] lets a config with only routes or services validate.

The diff adds no tests or fixtures for the served schema, and no test exercises the new null, anchor-key or integer max_request_size cases. I checked these cases by hand with a draft-07 validator in scratch. The PR description still lists null fields, nested . keys, integer max_request_size, rule-level dynamic_compression, configs without applications, and custom profiles as rejected, but this push now accepts all of them.

Review details

Review 5 of 10 for this pull request · View the full run

Comment thread resources/validation/upsun.json
@Theosakamg
Theosakamg merged commit 01324c2 into master Oct 16, 2026
7 checks passed
@Theosakamg
Theosakamg deleted the fix/upsun-schema-from-cli branch October 16, 2026 15:11
pjcdawkins added a commit to upsun/cli that referenced this pull request Oct 16, 2026
…fig (#248)

* feat(lint): use the schema from meta.upsun.com as the canonical Flex schema

The Flex config schema now comes from https://meta.upsun.com/schema/upsun
(the upsun/meta repository), which was synced with the CLI's copy in
upsun/meta#220. Schema changes are made there first.

- Add `make update-schema` (internal/lint/schema/gen.go), which fetches the
  schema and indents it, and `make check-schema`, run in the non-blocking
  upstream CI job beside `check-registry` (renamed `lint-upstream`).
- Replace the schema's remote $refs (runtime-versions, service-versions)
  with empty schemas when loading, so lint does not need the network.
  Types are checked against the registry by CheckTypes. A test fails if
  the schema gains another remote $ref.
- Report the schema's "maxItems: 0" error on a stack list with the same
  message as checkStackList.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lint): leave stack lists to checkStackList, so semantic checks still run

A schema error stops CheckContent before the semantic checks, so reporting
the schema's "maxItems: 0" stack error hid every other error until the
stack was fixed. The schema error is now skipped, and checkStackList
reports the stack list with the other errors, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(lint): report Flex stack lists with the schema's own error

Use the upstream schema's "maxItems: 0" error for a non-empty stack list,
rather than skipping it for checkStackList, which now only reports stack
lists in Fixed config. As with any schema error, the semantic checks run
once it is fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lint): check Fixed config with the Flex schema and an overlay

The platform validates Fixed and Flex config with the same models, so
Fixed config is now checked with the upstream Flex schema, patched by
fixed-overlay.json (a JSON Merge Patch), instead of three schemas
maintained here, which had fallen behind (e.g. post_start,
dynamic_compression, runtime extensions).

The overlay adds the keys that only Fixed config accepts: `disk` on
applications, web, workers and services, an application `size` and
`name` (required, as on the platform), and `build.caches`.

Behavior changes for Fixed config, matching Flex and the platform:
- `firewall` is rejected (deprecated in favor of `egress`).
- `type` is required, also with a `stack`.
- A non-empty stack list gets the schema's error, so checkStackList and
  the stack-without-type warning are removed.

The schema tests now also check the patched schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants