ci: pytest + alembic check gates; reconcile index drift #21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/ci-pytest-and-index-drift"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two hygiene fixes. No behaviour change to the application: the model edits emit no DDL and no migration is added.
Deliverable 2 — the two index drifts
uv run alembic checkfailed on two indexes that exist in the database and in no model. Both were created by this repo's own migrations, which makes the migrations the truthful side and the models the drifted one. Aligned the models; no migration written,alembic headsunchanged atc5e19a7d3f60.billing_profile_invoice_prefix_keyf64a7ec6edcfUNIQUE (invoice_prefix) WHERE deleted_at IS NULL__table_args__at allix_invoice_status001_initial_invoice_tablesinvoice(status)statushad noindex=TrueVerified against production, not inferred from it.
contabo-sg,dokku-postgres-haskos-db, read-only:Both live, both matching the new declarations. The compiled model DDL (
CreateIndex(...).compile(postgresql)) was diffed againstpg_get_indexdefon a scratch database and matches character for character modulo Postgres's canonical formatting (public.qualification,USING btree, parenthesised predicate).That last check is worth more than
alembic checkpassing, and is why it was done separately: alembic does not diff a partial index'sWHEREclause. A model declaringbilling_profile_invoice_prefix_keywithoutdeleted_at IS NULLwould also have satisfied the gate — while silently disagreeing with production about whether retiring a billing profile releases its invoice prefix for reuse. The predicate is load-bearing and is now stated in the model with the reasoning attached.Deliverable 1 — the CI jobs
New
Testsjob,runs-on: ci, gated ondetect-changes.outputs.has_codelike its siblings. It runs the full suite — 180 tests, including all 22 DB-backed ones — against apgvector/pgvector:pg16service container on--tmpfs, mirroring the pattern already proven inhaskos-engineering.Steps:
uv sync→alembic upgrade head→alembic check→pytest tests/ -q -rs.Three decisions worth reviewing:
alembic checklives here rather than in its own job because it needs a migrated database, which this job already has. It is only safe at all becausealembic/env.pyfilters autogenerate toOWNED_TABLES— unfiltered, it proposes dropping 52 tables belonging to five other applications.skipif(not HASKOS_TEST_DATABASE_URL)on the DB tier, so a skip in CI means that tier vanished rather than a test being excused. TheMigrationsstep cannot catch this — it readsHASKOS_DATABASE_URL, a different variable, so a typo in the test URL leaves the schema built, migrations green, and 22 tests quietly gone. That is the shapehaskos-engineeringshipped (432 passed, 189 skippedin CI against618 passedlocally), and it survived because the number on the left is the one people read. No|| trueanywhere; verified by running the step's exact shell with the variable unset — it exits 1.deploynowneeds: [test, docker-build]. Withneeds: [docker-build]alone, a red suite would have deployed to prod beside a green build and the gate would have been decorative.needs:treats skipped as not-satisfied, so a docs-only push still skips the deploy exactly as it does today.HASKOS_DEBUG: "true"is set on this job and is deliberately not set on the smoke test below it. The smoke test exists to boot the way production boots, so it must not skip the secret validation production runs. This job exercises the domain layer against a scratch database and has no production shape to reproduce —haskos_kernel.configwould otherwise reject the placeholder secrets at import and never reach a test.Spec Drift Callouts
UV_INDEX_HASKYTECH_PASSWORDalone is not enough. The brief says to mirror the Docker Build job, which passes only the password — that works there because the Dockerfile suppliesUV_INDEX_HASKYTECH_USERNAME=__token__. A bareuv syncon the runner has no Dockerfile behind it, so this job sets both. Copying Docker Build literally would have failed to resolvehaskytech-haskos-kernel.|| true" instruction — a suite that silently skips its DB tier is a suppressed gate wearing a passing summary line.deployneedingtestis an addition, not in the brief. Same reasoning: without it the new gate does not gate the deploy.Out of scope, flagged not fixed
DockerfileHEALTHCHECKusescurl -fwith--start-period=30s. Org convention iswget -qO /dev/nulland 120s+ for a Python app. Untouched — unrelated to either deliverable, and changing the health probe of a deployed app is not a hygiene edit.Testsjob will not be a required status context until branch protection is updated for this repo; it blocks nothing on its own.~/bin/haskos-dbis broken — it discovers databases from the Coolify API and the fleet has moved to Dokku, so it dies injson.loadon an empty response. The prod verification above went viassh contabo-sg+docker execinstead.Validation
ruff check app/ tests/ scripts/ruff format --check app/ tests/ scripts/uv run pytest tests/ -quv run alembic checkNo new upgrade operations detected.uv run alembic headsc5e19a7d3f60 (head)— one head, unchangedpg_get_indexdefcontabo-sgci.ymlparsesyaml.safe_load, jobs andneedsassertedRun against local PostgreSQL 17, scratch database
haskos_fin_ci.Do not merge without review — merging auto-deploys to production.