chore(ops): self-hosted release/ops acceptance gate and runbook#200
Merged
parthrohit22 merged 1 commit intoJul 26, 2026
Merged
Conversation
scripts/validate-compose.mjs now runs a full in-container acceptance sequence after /ready returns 200, instead of only checking readiness: re-runs and verifies Alembic migrations (core tables exist, a downgrade/upgrade round trip is clean, and a live connection through a percent-encoded DATABASE_URL password succeeds -- the Second-Origin#110 regression, proven against an isolated scratch role/database rather than only the existing offline unit test); registers two disposable users and proves owner isolation (a non-owner gets 404, never 403); runs a real analysis to a sealed ri.v1 snapshot and polls status with the frontend's resilient-polling shape (capped backoff on transient errors, immediate stop on any terminal state); restarts the api container and confirms the repository, analysis, and sealed snapshot all survive with no orphaned job rows; proves a pg_dump/pg_restore round trip preserves a sealed snapshot; and checks docker compose logs for the secrets this run generated. It always tears the stack down and exits non-zero on any assertion failure. Add docs/operations/SELF_HOSTED.md (linked from docs/README.md): required environment variables, security boundaries, unsupported deployment modes, and backup/restore expectations for the self-hosted stack. Testing performed: - npm run docker:validate, twice from a clean teardown state: both exit 0, full sequence passes. - cd apps/backend && PYTHONPATH= .venv/bin/python -m pytest: 0 failures, 4 expected env-gated skips (unaffected by this change).
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
Extends the local Docker Compose acceptance script into the repeatable
clean-clone release/ops gate issue #115 asks for, and adds the self-hosted
operations runbook it requires.
Linked issue
Closes #115
What changed
Infra (
scripts/validate-compose.mjs) — after/readyreturns 200, thescript now runs a full in-container acceptance sequence instead of just
tearing down:
alembic upgrade headin the liveapicontainer and asserts
repositories/analysis_jobs/ri_snapshotsexist;proves a
downgrade -1/upgrade headround trip is clean; proves thebug: alembic upgrade crashes when DATABASE_URL contains '%' (env.py interpolation) #110 percent-encoded-password regression against a real live
connection (not just the existing offline unit test) by creating an
isolated scratch Postgres role/database whose password contains a literal
@percent-encoded as%40inDATABASE_URL, running the full migrationset through it via
docker compose exec -e DATABASE_URL=... api alembic upgrade head, then dropping the scratch role/db.imports a real tiny public GitHub repo (
octocat/Hello-World, alreadythis API's own OpenAPI example); asserts user2 gets exactly
404(never403) reading user1's repository./statustocompletedusing the frontend's resilient-polling shape (cappedexponential backoff on transient network errors, immediate stop on any
terminal status, bounded deadline — mirrors
useAnalysisPipeline.ts's1ca0daf/bug: analysis page reports 'Analysis Failed' when a status poll drops, while the durable job is still running #164 design); asserts a real sealedri.v1snapshot exists viaGET /analysis/{id}/revision-manifest(snapshotSchemaVersion,canonicalGraphHash,sealedAt,verificationState: "verified").docker compose restart api, re-waits/ready,asserts the repository/analysis/sealed-snapshot hash are unchanged, and
queries
analysis_jobsdirectly overpsqlto assert exactly one row incompletedstate (no orphaned/in-flight job).pg_dump/pg_restoreround trip into ascratch database; asserts the restored
ri_snapshots.canonical_graph_hashmatches the original. The
ri.v1sealed-snapshot consumer path is alreadyfully live (since bug: sealing a snapshot fails the entire analysis when a dependency is declared in more than one manifest #156/feat(dependencies): serve the dependency graph from the sealed ri.v1 snapshot #158), so this is implemented and asserted for
real, not deferred.
docker compose logsonce before teardown andasserts none of this run's generated secrets (both user passwords, the
scratch DB password) appear verbatim.
It always tears the stack down in
finally(docker compose down -v) andexits non-zero on any assertion failure.
Docs — new
docs/operations/SELF_HOSTED.md, linked fromdocs/README.md's index: required environment variables, securityboundaries (owner-isolation 404 semantics, Fernet provider-key encryption, AI
egress allowlist — links the existing
AI_PROVIDER_EGRESS.mdrather thanduplicating it), unsupported deployment modes, and backup/restore
expectations for sealed snapshots.
No application code changed.
Acceptance criteria completed
command sequence (
npm run docker:config→docker:up→docker:validate).environment (
docker:validateruns the real Compose stack with both)./ready+ container healthchecks; now exercised by the gate).URLs, against a real live connection.
end-to-end.
corruption.
tested (the snapshot path is live).
boundaries, and unsupported deployment modes.
Testing performed
No frontend files were touched, so frontend lint/test/build were not run
(nothing to verify).
Screenshots
Not applicable (no UI change).
Security and data considerations
assertion is that a non-owner gets
404(never403) on another user'srepository, end-to-end against a real running stack.
isolated scratch role/database created and dropped within the same run —
it never touches the main app database or any real credential.
freshly generated per run and asserted absent from
docker compose logsbefore teardown.
.envfiles, or credentials are committed.Dependencies and blocked work
None.
Scope changes or remaining work
.github/workflows/ci.yml'sdocker-composejob currently hand-rolls itsown up/wait-for-ready/down steps rather than calling
scripts/validate-compose.mjs, so CI does not yet run this deeperacceptance sequence — only the local/manual gate does. The acceptance
criterion ("Postgres and Redis paths are exercised in CI or an equivalent
isolated acceptance environment") is satisfied by the script itself as
written; wiring CI to call it too is a reasonable low-risk follow-up, but is
left out of this change to keep it scoped to what #115 asked for (extend the
script + add the runbook) rather than also changing CI behavior.
Contributor checklist
devupstream/devupstream/devCloses) is used only because the issue is fully resolved