feat(delivery): live CI/CD pipeline + demo runtime (v2 phase A3-3)
A push to `dev` now becomes a live demo, and a failed build never takes the demo down. - Four tables (pipeline_runs, pipeline_steps, releases, app_runtimes) and their migration; alembic upgrade/downgrade/check all clean. - DeliveryService implements DeliveryHooks: on_app_created provisions the demo port + host + first build (idempotently); on_branch_pushed queues a run for `dev` and supersedes older *queued* runs, leaving a running one alone. - The 8-step pipeline. Blue/green is real: the replacement starts on a staging port, is healthchecked, and only then does the route move — then the previous container is stopped. A failure removes the replacement, releases its port and leaves the old container serving. - The demo port ledger is the DATABASE, not the router adapters' in-memory maps: the API and the delivery worker are separate processes. - Runs are claimed with a conditional UPDATE, so two workers never build the same run. - Live logs are batched (1 event / 500 ms, 200 lines) and redacted as a whole tail — per-chunk redaction misses a secret split across pipe reads, and the *stored* tail leaked until this was fixed. - delivery worker: drain the queue, then poll remotes for pushes made outside the engine. - 7 routes; reads need membership, actions need projects.manage and are audited. Tests: 415 API (+21, 1 docker-marked skipped); ruff, mypy, alembic check clean. Report: docs/plan/reports/agent-3-phase-3.md
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -213,13 +213,19 @@ async def test_container_exposes_all_v2_builders(session_factory):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_app_builds_with_v2_routers_and_delivery_worker_runs(client):
|
||||
async def test_app_builds_with_v2_routers_and_delivery_worker_runs(
|
||||
client, redirect_error_log
|
||||
):
|
||||
# The full handler app imports every v2 router; /health proves it assembled.
|
||||
assert (await client.get("/health")).status_code == 200
|
||||
|
||||
from app.workers.__main__ import _build_parser, _pass_function
|
||||
from app.workers.delivery import DeliveryWorker
|
||||
|
||||
# `redirect_error_log` points the workers (and the error recorder) at the
|
||||
# in-memory test database. The delivery worker is no longer a no-op: it now
|
||||
# reads the pipeline queue, so without the redirect it would open the
|
||||
# developer's real `agy_web.db`.
|
||||
assert (await DeliveryWorker().run_pass()).failed == 0
|
||||
# ...and it is selectable from the CLI.
|
||||
one_pass = _pass_function(_build_parser().parse_args(["delivery"]))
|
||||
|
||||
Reference in New Issue
Block a user