Skip to content

Validate Domain routes (S-003-1, S-003-8..12) - #168

Merged
shipmill-romamo[bot] merged 2 commits into
mainfrom
feat/159-domain-routes-validation
Oct 30, 2026
Merged

shipmill-romamo[bot] merged 2 commits into
mainfrom
feat/159-domain-routes-validation

Conversation

@shipmill-romamo

Copy link
Copy Markdown
Contributor

Summary

A Domain can declare spec.routes (spec S-003): proxy routes (path + upstream {address, port}) and static routes (path + static {component, directory, spaFallback?}), exactly one of upstream or static per route. This PR is the schema, the parser and cloudfall config validate; rendering is #162.

  • config/schemas/v1/domain.schema.json: routes (at least one item) with $defs for the route, the upstream (now shared with proxy.upstream, same shape) and the release-relative path. directory and spaFallback are held by the schema: letters, digits and ._-/, relative, no .. segment, and directory not the release root (., ./, ././). path is only a string in the schema, so its rules refuse with their own codes
  • sdk/src/cloudfall/domain.py: DomainRoutePath (/ then letters, digits and ._~/-; is_reserved, is_directory) and ReleaseRelativePath (is_release_root)
  • sdk/src/cloudfall/inventory.py: DomainProxyRoute, DomainStaticRoute, DomainInventory.routes (default empty). as_dict() adds routes only when there are some, so a Domain with only proxy.upstream serializes byte-for-byte as before (Q-3)
  • sdk/src/cloudfall/validation.py: _validate_domain_references checks routes in this order: domain_route_source_invalid (both proxy.upstream and routes; neither stays valid), domain_route_path_invalid, domain_route_path_reserved (/.well-known/acme-challenge or under it), domain_route_path_duplicate, then for a static route the trailing / (domain_route_path_invalid), resource_reference_missing and domain_route_component_not_on_proxy. Every refusal exits CONFIG_INVALID

The engine needs no change for CI: config/examples has no routes, and domains.yml's early check already refuses a Domain without proxy.upstream before any change, so a routes-only Domain stops there until #162.

Plan

  • Serves: G-3
  • Files: config/schemas/v1/domain.schema.json, sdk/src/cloudfall/domain.py, sdk/src/cloudfall/inventory.py, sdk/src/cloudfall/validation.py, sdk/tests/test_domain_routes.py, changelog.d/159-domain-routes-validation.md
  • Order: failing tests for each criterion first (sdk/tests/test_domain_routes.py); then the routes schema, the route value objects, DomainInventory.routes, and the _validate_domain_references route checks; the spec's Behaviour covers the rules
  • Proof: S-003-1, S-003-8, S-003-9, S-003-10, S-003-11, S-003-12, each a test_s003_<k>_... test running cloudfall config validate; the existing inventory and validation tests prove a Domain with only proxy.upstream is unchanged (Q-3)

Test plan

  • New sdk/tests/test_domain_routes.py (28 tests), failing on main at import, passing here; specs.py coverage --spec 003 lists a test for each of S-003-1, 8, 9, 10, 11, 12
  • uv run ruff check ., uv run mypy, uv run cloudfall config validate --project config/examples: pass
  • Touched and domain-related tests pass (165): sdk/tests/test_validation.py, test_inventory.py, test_domains_apply.py, test_domains_converge.py, test_domains_observe.py, test_service_evidence.py, test_operations_dashboard.py, test_agent_server.py, engine/tests/test_nginx_site_templates.py, test_domains_one_domain.py, test_ansible_inventory.py
  • The full suite is left to CI

Fixes #159
Part of #90

🤖 Generated with Claude Code

shipmill-romamo Bot and others added 2 commits October 30, 2026 15:56
A Domain can now declare spec.routes: proxy routes (path + upstream) and
static routes (path + static component, directory, spaFallback). The schema
holds each route's shape and the release-relative directory and fallback;
config validate checks the route source, path syntax, reserved and duplicate
paths, and that a static component exists and is deployed on the proxy
server. The inventory parses routes into value objects and serializes them
only when present, so a Domain with only proxy.upstream is unchanged.
Rendering routes comes with #162.

Fixes #159
Part of #90

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The releasePath schema pattern ends with $, which Python's re.search lets
match before one trailing newline, so config validate accepted
directory: "web\n" that ReleaseRelativePath then refused with a ValueError
when the inventory loaded. Refuse control characters in the schema, as
component.schema.json's controlCharacterFree does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot merged commit 835d7ce into main Oct 30, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the feat/159-domain-routes-validation branch October 30, 2026 14:25
shipmill-romamo Bot added a commit that referenced this pull request Oct 30, 2026
…11) (#171)

* Accept a command health check in the component schema (#170)

A component can declare healthCheck type: command (command argv,
timeoutSeconds 1-300, attempts 1-20, no other key). The SDK parses it into
value objects and the deploy, restart and rollback plans name the check and
its command in wouldRun; the plan JSON gains no field (S-009-1, S-009-13).

The engine does not run the check yet (#164), so the deploy role's entry
points refuse a command check on their first task instead of passing an
unrun gate.

Fixes #161

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a bad --source-schema with PRECONDITION and add migrate --data-schema (S-007-12, S-007-13) (#169)

* Refuse a bad --source-schema with PRECONDITION and add migrate --data-schema

A schema name must match ^[a-z_][a-z0-9_]*$ and be at most 63
characters, and may be named once; a malformed or repeated name now
exits PRECONDITION from the command before any playbook, as spec 007
says, where it was a usage error and a repeat was silently dropped.
cloudfall migrate takes --data-schema DATABASE=NAME beside --data, so
the data step copies only the named schemas as data copy
--source-schema does; one for a database with no --data is refused
before any step (S-007-12, S-007-13).

Fixes #126

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

* Describe a resumed migrate step by this run's options

A pending step of a persisted plan kept the description it was saved with,
so a data step saved with --data-schema crm=public and resumed without it
reported "schema public" while it copied every schema (#126, S-007-13).

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

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Validate Domain routes (S-003-1, S-003-8..12) (#168)

* Validate Domain routes (S-003-1, S-003-8..12)

A Domain can now declare spec.routes: proxy routes (path + upstream) and
static routes (path + static component, directory, spaFallback). The schema
holds each route's shape and the release-relative directory and fallback;
config validate checks the route source, path syntax, reserved and duplicate
paths, and that a static component exists and is deployed on the proxy
server. The inventory parses routes into value objects and serializes them
only when present, so a Domain with only proxy.upstream is unchanged.
Rendering routes comes with #162.

Fixes #159
Part of #90

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

* Refuse a trailing newline in a static route's directory or spaFallback

The releasePath schema pattern ends with $, which Python's re.search lets
match before one trailing newline, so config validate accepted
directory: "web\n" that ReleaseRelativePath then refused with a ValueError
when the inventory loaded. Refuse control characters in the schema, as
component.schema.json's controlCharacterFree does.

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

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a data copy the target can't hold before dumping it

A preflight on the target host, after the empty-target check and
before pg_dump, reads the source and target catalogs and names every
schema the source lacks, extension and policy role the target lacks,
and auth. reference, taking every name only as a psql variable. Any
finding refuses the copy with nothing dumped: data copy and migrate's
data step exit PRECONDITION with data_copy_preflight_refused and the
four lists, read whole from a findings file under --data-migrations.
pg_dump takes the needed extensions with named schemas, and pg_restore
runs in one transaction, so a failed restore leaves no table and no
receipt. Spec 007 is built (S-007-6 to S-007-11).

Fixes #125

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

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.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.

Validate Domain routes (S-003-1, S-003-8..12)

0 participants