On this page
Database schema releases
A schema release declares its engine. Stado verifies SQLite and PostgreSQL schemas as well as Supabase projects; it does not treat every database as a Supabase project. The worker runs these operations from the product's release manifest. The engine is required, and an unsupported engine is refused.
Build and verify
For SQLite or PostgreSQL, build the schema artifact through the normal source-bundle operation:
stado product source-bundle --name database-schema.tar --include migrations
stado product schema verify --engine sqlite
stado product schema verify --engine postgres
The release worker supplies WISENT_SOURCE_DIR, WISENT_PRODUCT, WISENT_VERSION and an absolute WISENT_OUTPUT_DIR. Stage release/database-schema.tar as database-schema.tar. Verification reads that built artifact, not SQL from the worker's current checkout. Changing the checkout after the build cannot change what verification applies. --migrations DIR selects a directory under the bundle's source/ root; absolute paths and parent traversal are refused. Files ending in .sql run in file-name order, and an empty migration directory is refused.
SQLite uses the bundled SQLite driver and creates $WISENT_OUTPUT_DIR/schema-verify/scratch.sqlite. Its report is schema-verify/schema-verify.json, with the applied versions and actual table names. PostgreSQL requires psql on the worker and WISENT_SCRATCH_DATABASE_URL naming a dedicated, empty scratch database. Verification applies the built migrations there and writes schema-verify.json. It does not create or empty that database, so the worker must provide an isolated database, never a production URL.
Each migration runs in a transaction. PostgreSQL passes the migration as an actual psql --file operation with --single-transaction and ON_ERROR_STOP; plain standard input would not activate that option. Migration files must not issue their own transaction-control commands. A SQL failure names the migration and carries the database's actual error. Prior successful migrations remain available for diagnosis; the failed transaction is rolled back.
Deliver PostgreSQL
stado product deliver schema --engine postgres
The release worker supplies WISENT_RELEASE_ARCHIVE, WISENT_RELEASE_SHA256, WISENT_RELEASE_URI, WISENT_PRODUCT, WISENT_VERSION, WISENT_OUTPUT_DIR, and the destination through SCHEMA_DATABASE_URL. Stado verifies the archive's digest and extracts its one database-schema.tar before contacting that database. It refuses missing identity fields before writing migration history. A mismatched digest, missing or duplicate schema bundle, unsafe archive member, or invalid migration path applies no schema.
Delivery records each version in wisent_schema_migrations in the same transaction as that migration. Repeating the same release skips recorded versions without inserting the rows again. schema-receipt.json records the product, version, release URI and digest, applied versions and versions already present. A failed migration does not acquire a history row. A successful receipt describes delivery, not publication or channel promotion; those remain the release pipeline's responsibility.
SQLite delivery is refused with a SQLite schema has no delivery: the owning application applies its file database migrations when it opens the database. An unsupported engine and an omitted --engine fail at argument parsing.
Supabase projects
stado product schema verify --engine supabase
stado product deliver schema --engine supabase
Supabase keeps its existing supabase-source.tar artifact, Docker-backed scratch verification, project credential bindings and migration history adapter. Both steps run the Supabase CLI release Stado declares in stado-rs/data/declarations/supabase-cli.json, never whichever supabase the search path holds: a host that lacks it, or holds another release, installs it first under ~/.local/share/stado-supabase/<version>/ from the CLI's GitHub release, verified against that release's checksums.txt. A failed install names its step (fetching <url>: …, <archive> has sha256 <actual>; <checksums url> lists <expected>, unpacking <archive>, <binary> was installed but does not answer <version> to --version). --project-dir web selects a project bundled below source/web; the default is the bundle root. Delivery consumes the verified release archive and the declared SUPABASE_PROJECT_REF, SUPABASE_ACCESS_TOKEN and optional SUPABASE_DB_PASSWORD. It writes supabase-receipt.json. Missing Docker, credentials or provider access is a failed or blocked real operation, not a successful schema check; a host without a Docker daemon fails verification with the CLI's own Docker sentence.
The worker must also supply WISENT_PRODUCT, WISENT_VERSION,
WISENT_RELEASE_URI and an absolute, writable WISENT_OUTPUT_DIR. Missing or
blank release identity is refused by variable name before linking the project,
changing migration history, applying migrations, publishing secrets or deploying
functions. A delivery cannot mutate the project first and discover afterward
that it cannot identify its receipt.
An unset SUPABASE_DB_PASSWORD explicitly selects the Supabase client's
Management API authentication. A supplied empty, whitespace-only or non-Unicode
password is a named refusal before project access, not a reason to choose that
other method. Valid password bytes are passed unchanged; diagnostics do not
include the password value.
Workspace cleanup is part of delivery completion. If removal fails after the project was changed, the command refuses with that state, the workspace path and the actual filesystem cause. If delivery and cleanup both fail, both causes remain in the error chain; no successful receipt is written for that attempt.
Migration history declarations
Delivery loads the optional supabase/split-migrations.json and
supabase/baseline.json from the verified bundle before linking the project.
The split document requires a parts object mapping each part's version to
its original version. The baseline requires applied and retired arrays of
version strings; use an empty array when that list has no entries. Explanatory
metadata such as why remains permitted.
A missing file means no declaration of that kind. An unreadable file or a directory at that path is an error, not an empty plan. Invalid JSON, missing required fields, wrong field types and blank versions refuse delivery with the declaration path and the parser or validation cause. Fix the bundled declaration and publish its corrected source; changing the worker's checkout does not change an already verified archive.
Applied versions are read from the selected project's
Management API migration history,
GET /v1/projects/{ref}/database/migrations, using the delivery's
SUPABASE_ACCESS_TOKEN. The token needs database:read, or the fine-grained
database_migrations_read permission. This read happens before linking and
history repair. It does not interpret the CLI's display table as JSON.
Transport failures name the operation and project URL; provider refusals
retain HTTP status and response; malformed history names its decoding cause.
There is no alternate project, credential or empty-history fallback.
Database mutation commands still use the explicitly selected password or
Management API authentication described above.
The successful receipt includes history_before, the applied versions read
before reconciliation. repaired describes the declared history changes,
not proof that every later migration or function deployment succeeded.
Partial delivery evidence
Every admitted attempt writes a separate
supabase-progress-<attempt>.json under WISENT_OUTPUT_DIR; stderr names
its exact path. It binds the product, version, project and release digest.
Before a provider operation starts, its step is persisted as in_progress;
afterward the step records completed and the observed result, or failed
with the complete error chain. Operations cover history reads, project linking,
each declared history repair, migration push, function-secret publication,
function deployment and writing the final successful receipt.
Workspace cleanup is attempted even after a failed provider operation. Its
path, outcome and filesystem cause are retained separately. The attempt's
final state is failed whenever delivery or cleanup failed; earlier completed
steps remain readable. A process interrupted during an operation leaves that
step in_progress, which means its external outcome is unknown, not that
nothing changed. Recording failures preserve the original operation's error
alongside the recording cause.
These files survive workspace removal and repeated attempts. Release status and the selected pipeline run in native Releases show the delivery's exact output prefix and its accepted outer receipt digest, when one exists. The progress files live under that output prefix; they are diagnostic evidence, not successful delivery receipts. A completed provider step does not permit dependent deliveries to proceed; the outer release worker's validated delivery receipt remains the admission proof. Credential values are not added to the progress identity or function-secret result.
Edge function credentials
Bundle supabase/function-secrets.json alongside supabase/functions. It is a
JSON array of environment names, for example
["WISENT_IDENTITY_URL", "WISENT_IDENTITY_ANON_KEY"], never credential values.
Include the file explicitly in source-bundle --include and bind each name in
the delivery's secret_env to its Skarbiec role and field. The delivery reads the
declaration from the digest-verified release, not the current checkout, and
resolves every named value before any remote mutation. Missing values, duplicate
names, invalid environment names and provider-reserved SUPABASE_ names are
refused with the declaration and credential name.
After migrations succeed and before functions deploy, Stado sends those values
to the Supabase Management API. Its access token needs
edge_functions_secrets_write (OAuth scope secrets:write). A provider refusal
includes the operation, project, HTTP status and response; there is no retry or
manual-secret fallback. Existing unrelated secrets are not removed. An omitted
declaration means this release manages no function secrets.
supabase-receipt.json includes function_secrets with the declared names,
provider acceptance state and HTTP status, never values. The same release
output is available in the native Releases screen. Provider acceptance is not
consumer proof: execute the installed function and inspect its resulting
application state. For canonical identity exchange, the schema product's real
tests/auth/exchange.mjs journey checks the issued token and unchanged owned
rows against both actual auth services.
Every manifest names schema and its explicit engine; schema verification and delivery have no engine-specific commands. Artifact names and Supabase production delivery dependencies are the same whatever engine the manifest names.
Desktop and diagnostics
These are release-worker steps, not permission to send a production database URL to an arbitrary desktop-selected host. A schema product's declared build, test and delivery steps run through the normal release pipeline. Read their command output, nonzero status and retained evidence in the release run; the native Releases surface uses the same run records. A declaration or a successful build is not proof that migrations reached the destination. The receipt and the database state provide that proof. A graphical review of the actual release screen remains part of release qualification.
Real qualification
The Stado repository keeps the CLI journeys under tests/schema_release/:
cargo test --manifest-path stado-rs/Cargo.toml --test schema-release-journey -- --nocapture
The SQLite journey needs the real sqlite3 client for independent inspection. The PostgreSQL journey needs real initdb, postgres and psql programs on PATH; STADO_TEST_POSTGRES_BIN can select their installed directory. It creates its own PostgreSQL cluster, accepts only a private Unix socket, creates separate verification and delivery databases, then drops those databases and stops only its own child process. No operator database or fleet configuration is used.
The journeys invoke the candidate Stado CLI to build the source bundle, verify it and deliver it through the worker's artifact interface. They inspect real persisted rows and tables, changed-checkout isolation, repeat delivery, failed-migration rollback, unchanged state after a bad archive digest or missing metadata, unsafe migration paths, missing engines and the SQLite delivery refusal. The local artifact envelope is not a fleet-published release and these journeys do not claim provider, GUI or production promotion qualification. Supabase products also run their manifest's real schema verify --engine supabase step with the actual CLI and Docker dependency.
For declared Supabase function secrets, run
node --test tests/schema_release/supabase-secrets.mjs with STADO_TEST_BINARY
and STADO_SUPABASE_SECRETS_FIXTURE. The private JSON fixture declares
isolated: true, projectRef, sourceRevision, product, version,
releaseUri, releaseArchive, releaseSha256, secretNames, probePath,
probeKeyEnv, and the exact JSON expectedBefore, expectedInitial and
expectedRotated responses of a real protected Edge consumer in that dedicated
project. originalSecretEnv, initialSecretEnv and rotatedSecretEnv map each
declared name to an injected environment variable, not a literal secret.
The candidate must identify the checkout's committed revision. The journey
refuses missing declared values and missing or blank product/version/release
identity, proving the installed consumer stayed unchanged in each case. Identity
refusals carry otherwise valid, changed credentials, so an unchanged consumer is
not inferred merely from a failed command. The same journey checks empty and
whitespace-only selected-password refusals against the unchanged real consumer.
The fixture also supplies declarationRefusals, keyed by
split-parts-missing, split-parts-type, split-original-type,
split-blank-part, split-blank-original, baseline-applied-missing,
baseline-retired-missing, baseline-applied-type, baseline-retired-type,
baseline-version-type and baseline-blank-version. Each entry declares
releaseArchive and releaseSha256 for a real release archive containing that
invalid declaration, with the same otherwise valid project and function-secret
inputs. Paths are relative to the fixture file. Each attempted delivery must
name the declaration and its cause, write no successful receipt and leave the
real consumer unchanged. The report retains each archive digest and command.
expectedMigrationVersions declares the complete applied-version list expected
after successful delivery. The journey independently reads the real project's
migration history before and after delivery, checks the receipt's
history_before against that observed state, and checks that each malformed
declaration left migration history unchanged as well as the Edge consumer.
It then delivers the actual release with initial and rotated credentials,
checks the receipt and real consumer response, and restores and verifies the
original secret state. Supply
SUPABASE_ACCESS_TOKEN and any project password through
Skarbiec. No project is provisioned or selected implicitly, and no provider is
simulated. Reports, candidate digest, commands, exit statuses, response bodies
and receipts remain under .build/supabase-secrets/. This journey performs real
schema deliveries; its isolated project must be explicitly reserved for it.
Reports under the checkout's ignored .build/schema-* directories retain the exact source revision, source patch, binary digest, commands, exit statuses, database logs, artifacts, receipts and database state. A missing dependency fails the journey and retains the actual refusal; it is never reported as passed. The generated CLI reference must be refreshed from the candidate binary when that binary can be built, rather than hand-editing help captured from an older executable.
Source: this website