Repository navigation
Refuse a data copy the target can't hold before dumping it (S-007-6..11) - #171
Merged
Merged
Conversation
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>
…-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) 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>
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>
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds S-007-6 to S-007-11 of
docs/specs/007-data-copy-schemas.md: thedata copypreflight and a restore that leaves no half-restored target. With #169 (S-007-12, S-007-13) every criterion of the spec is delivered, so this PR sets the spec tobuiltand fills its Verification section.cloudfall_data_migration, after the empty-target check and beforepg_dump): one source query and one target query, each answering a JSON object line. Every name reaches SQL only as a psql variable (:'cloudfall_data_schemas',:'cloudfall_data_every_schema',:'cloudfall_data_extensions',:'cloudfall_data_policy_roles'), as Copy only the named schemas in data copy (#124) #134 does; the source side keeps the URL in$(cat <file>), and the target side passes the source's names ascommandargv, never in a shellmissingSchemas: a selected schemapg_namespacelacksextensions: one installed in a selected schema, or whose member object (pg_dependdeptypee) an object of a selected schema depends on (relations, types, functions, constraints, column defaults, triggers, view rules, policies), or with no--source-schemaevery extension butplpgsql; kept when the target'spg_available_extensionslacks itroles: roles inpg_policy.polroleson a selected table that the target'spg_roleslacksauthReferences: whenauthisn't selected,function <schema>.<name>(<args>)whoseprosrcmatches\mauth\.or that depends on an object inauth, andpolicy <name> on <schema>.<table>whose USING or WITH CHECK does<service>-<database>.preflight.jsonunder--data-migrations(the controller directory is now created before the block) and fails before the dump on any finding.migrate_dataremoves a stale file first, reads the file when the playbook fails, and raisesdata_copy_preflight_refusedwith the four lists in the error context;data copyexitsPRECONDITION, andmigratemaps thedata:<database>step's refusal toPRECONDITIONtoo (other step failures stayENGINE_STEP_FAILED). The file is removed after reading--source-schema,pg_dumpgets one quoted--extensionper needed extension.pg_restoreruns with--single-transactionLifecycleErrorgains an optionalcontext, merged into the error context;DataCopyPreflightFindingsreads the file strictly (exactly the four keys, lists of strings, elsedata_copy_preflight_findings_invalid)Plan
--extensionand--single-transactionargv, the SDK reading the findings file intodata_copy_preflight_refusedfordata copyand thedata:<database>step ofmigrate), then the role's source and target catalog queries taking every name as a psql variable, the findings file under--data-migrations, the refusal beforepg_dump,pg_dump --extension,pg_restore --single-transaction, then the SDK side; Behaviour as in spec 007, "Preflight before dumping" and "No half-failed restore"Test plan
engine/tests/test_data_migration_role.py, Ansible's own templating):test_s007_6_*(task order: empty-target check, preflight, findings file, refusal, thenpg_dump; names only as psql variables, a hostile name stays one quoted word),test_s007_7_*(target query readspg_available_extensionsfrom a variable;--extensionper needed extension only with named schemas; a hostile extension name stays one quoted word),test_s007_8_*,test_s007_9_*,test_s007_10_*(all findings in one message and one file),test_s007_11_*(pg_restore --single-transaction, the receipt task follows the block)sdk/tests/test_data_copy_preflight.py, a stand-in playbook that writes the findings file and fails): each list reaches the error context whole (400 role names, past the 2000-character tail), no receipt, no findings file left, the source URL's password never in the answer, a stale file never answers for a new run, a malformed file is refused, a failed restore without findings staysENGINE_STEP_FAILEDand the next run goes ahead, andmigrate's data step answersPRECONDITIONwith the same listsuv run ruff check .,uv run mypy,ansible-lint engine/ansible, and the touched test files pass locally; the full suite is left to CIspecs.py checkpasses;specs.py coverage --spec 007lists tests for every criterion but S-007-12 and S-007-13, whose tests are in Refuse a bad --source-schema with PRECONDITION and add migrate --data-schema (S-007-12, S-007-13) #169Not run against a PostgreSQL server: the catalog queries' results on a real source (an extension type two
pg_dependhops away,polroles,pg_get_exprtext matching\mauth\.),pg_dump --schema --extension(pg_dump 14 or later), apg_restore --single-transactionthat fails and rolls back, and the whole role over SSH.Fixes #125
🤖 Generated with Claude Code