Repository navigation
Description
Remove the unsafe deployment trigger and bootstrap the gate.
Later steps moved to other tickets: O1.2.3 to #788.
Branch rule: create feature branches from dev/streaming-architecture; every implementation PR targets it and is squash-merged. Each verified wave reaches main through one release PR from dev/streaming-architecture (merge commit, not squash; outside show times). After each release or main hotfix, a PR from main syncs dev/streaming-architecture. Wave 0 is the first release.
Plan status: plan v4 is jointly accepted by Claude and Codex. Acceptance permits a readiness check for this issue; it does not approve a merge to main or production activation. Tracking does not mark implementation or deployment complete.
Parent: #331
Epic: #312
Done when: all leaf checks below pass with linked evidence; any production/device conditions are verified or remain explicitly open.
Implementation details
Source: operations-v4.md, O1.2. Plan revision: 19b69ebe58cc85659ca264e16756846b39bb80e8.
Shared contracts, wave order and release gates · Code anchors.
Owner/files: operations; existing .github/workflows/backend.yml, backend/scripts/deploy_hetzner.sh, backend/docker-compose.prod.yml, docs/stream-rework/prod/; proposed docs/implementation/evidence/runtime-inventory.md and docs/implementation/runbooks/bootstrap-maintenance.md.
Dependencies: coding and read-only inventory start immediately. First gate activation needs the backend maintenance-gate implementation, but must not depend on future HLS or fallback. PR boundary: inventory/runbook plus a separate workflow-only protection PR. Rollback: retain automatic builds; never restore automatic service replacement as a rollback convenience.
Implementation and verification
- O1.2.1 Change the main-push workflow to build/push images without replacing API or restarting Liquidsoap; preserve image SHA/digest output and allow build cancellation independently. Test push/manual input cases and assert no mutating SSH, nginx provisioning, database initialization or media restart can execute from a build-only event. v4: the push that merges this change already runs the new workflow file, so the release carrying it deploys nothing; verify that push builds only. Editing the workflow does not stop an older run that is already mutating the host; list, drain or cancel and reconcile older runs before the first O1.2.5 deployment.
- O1.2.2 Wave 0 (v3). Define an explicitly logged legacy maintenance procedure, first written for installing the first gate-capable image: operator reserves a show-free window, temporarily denies new producer/capture requests and schedule mutations at the ingress, verifies no runnable/catch-up scheduled start can occur, and drains producer/rehearsal/capture/finalisation before snapshot and replacement. Specify exact deployed routes and keep stop/diagnostic paths usable. If the legacy scheduler cannot be bounded safely, use an announced bootstrap API outage after work drains, rather than inventing a pause control. Retain DB/config snapshot and rollback image; rehearse both paths without assuming the not-yet-installed gate, fallback or HLS already works. v3: this window is also the required procedure for every interim O1.2.5 deployment until O2 is active, not only for the first gate install: serialize deployments (one at a time), deny new producer/capture/schedule work at the ingress, drain existing work including pending finalization, bound internal scheduler and catch-up paths (or use the announced outage), then replace. The first O1.2.5 install, which brings the precheck endpoint, runs in this window without the endpoint. Verify: a start or schedule edit right after the ingress closes is refused; pending finalization delays replacement; a second concurrent deploy waits or is refused; none races a replacement.
- O1.2.4 Wave 0. v3: CI must run for PRs into and pushes on
dev/streaming-architectureas well asmain, with no production deploy fromdev/streaming-architecture. Adddev/streaming-architectureto thepull_requestandpushbranches offrontend.ymlwhile its Pages deploy stays limited tomain. Hand the required check names to the repository administrator for branch protection. Add a backend CI job on pull requests and pushes touchingbackend/**:cargo fmt --check,cargo clippy --all-targets --lockedandcargo test --lockedwith the pinned toolchain frombackend.yml. CI runs neither today;cargo clippy && cargo testappears only in the PR template. First record the baseline clippy result; if it fails, allow the recorded warnings once and ratchet to-D warningsin a follow-up leaf rather than blocking all work. Verify a PR with a failing test or a formatting error is red, for bothmainanddev/streaming-architecturetargets and for backend and frontend changes; a push todev/streaming-architecturenever deploys; and the job needs no secrets or production access. - O1.2.5 Wave 0. Keep one interim, operator-triggered deploy path until O2 is active, so Wave 0 fixes can ship after O1.2.1 removes push-triggered replacement: a
workflow_dispatchjob that requires a typed confirmation, deploys an exact image digest, and runs inside the O1.2.2 exclusion window: first the ingress closes new work and existing work drains, then a read-only pre-check on the host confirms it. The backend integrator adds a small loopback-onlyGET /api/internal/deploy-precheck(served on127.0.0.1:8000, denied by an explicit nginxlocation /api/internal/rule, and protected by a token from a root-readable host file, because the API sees the same Docker bridge address for nginx-proxied and local requests; no credentials in CI) returning active producer, rehearsal (is_test), recording/finalization in progress and the next scheduled start in UTC, resolved withscheduler::show_start_utc.GET /api/showsis not usable here because it requires an admin session. The job refuses when any work is active or a show starts within 30 minutes. v3: the pre-check alone is not enough: between check and replacement a producer, capture or schedule change could start, so the window, not the check, excludes new work. Deployments are serialized; a second concurrent run waits or fails. v4: before the first run, drain or cancel and reconcile any older queued or running deployment job from the old workflow; the first run is triggered explicitly after the first release push (which builds only, see R2.1.1). Bootstrap: the endpoint does not exist on the running image, so the first install uses the O1.2.2 window without it, and the job must handle a missing endpoint by refusing unless the operator confirms the manual window procedure. This endpoint is a precursor of B2.1.4 readiness and is replaced by it, not kept as a second authority. It runs only the API replacement part ofdeploy_hetzner.sh, never nginx setup or the Liquidsoap sync. Retire it when O2.1.3 lands. Verify the pre-check refuses during a fixture live show and before a fixture scheduled show; a missing endpoint is refused without the manual window confirmation; a start or schedule edit right after the pre-check cannot race the replacement; pending finalization and a second concurrent deploy are handled; an older deployment run cannot overlap the first manual run; and the job cannot run on push.
O1.2.5 contract and completion tracking
Contract v2 is agreed. Claude confirmed the deadline/diagnostic fields, complete upload-handler coverage and implementation order. The upload route-layer guard, token-directory mount and safe show-ID diagnostics remain part of that agreement.
The B1 configuration-validation addition is also confirmed by Codex. B1 must parse the full configuration strictly, preserve startup precedence, validate recovery mail/proxy settings and keep values out of diagnostics. Run it without database or network access. Duplicate keys warn in the general check; mail activation rejects duplicates of its four settings. Check retained host configuration before the maintenance window.
Contract agreement does not mark O1.2.5 Ready. The main-to-interim sync is tracked in the closed issue #731. B1 still requires the finalization fix at #724. B2 and ops work require merged B1; final ops wiring and end-to-end proof require merged B2. Leaf-specific readiness comments must name the actual prerequisite PRs and exact ownership.
The build-only hotfix #729 is merged into main; build-only push evidence is recorded. It did not activate O1.2.5. Production evidence and Anton's explicit activation approval remain required.
All remaining implementation PRs must use Issue: https://github.com/phaabe/live.moafunk.de/issues/381 (partial) while production evidence is outstanding. Existing implementation PRs #417, #714 and #718 now use that marker too, preventing older PRs from triggering premature closure. Leaf checkboxes require their evidence; a merged implementation is not production qualification.
Exact coding prerequisites
| Leaf | Prerequisites |
|---|---|
| O1.2.1 | O1.2.4 |
| O1.2.2 | No coding prerequisite listed; verify task and activation gates. |
| O1.2.4 | No coding prerequisite listed; verify task and activation gates. |
| O1.2.5 | O1.2.1, O1.2.2 |
Native blockers are a safe subset. No native blocker does not mean Ready. Per-leaf overrides, activation requirements and operator decisions still apply.
Claim the issue/leaf and file scope before editing. Record executor, worktree/branch, commit/artifact, tests and result. One editor owns each shared integration file. Check code anchors against the current branch and run GitNexus impact before code edits. Keep feature implementation and tests together. A merged PR is not proof of production or device qualification. PRs targeting this non-default branch may not auto-close issues; close an issue only after its required evidence is attached.
Activity
anneoneone commentedon Sep 27, 2026
Ready (first batch), executor Codex: O1.2.4, then O1.2.2 may start now.
O1.2.1 follows O1.2.4 in the next ops batch. O1.2.3 waits. O1.2.5 needs O1.2.1 and O1.2.2 and is split: Codex workflow, host script and nginx rule; Claude backend precheck endpoint.
Only the named leaves are ready. Rules: #312 (first batch comment).
anneoneone commentedon Sep 28, 2026
Codex claims O1.2.4 only. Executor: Codex. Lane: ops. Reviewer: Claude.
Branch:
feat/381-integration-ci. Base:dev/312-interim.Claim scope
Files:
.github/workflows/backend.yml,.github/workflows/frontend.yml,.github/workflows/epic-ci.yml,.github/workflows/README.mdand workflow regression tests under.github/workflows/tests/as needed.Worktree:
/private/tmp/moafunk-381-integration-ci.Readiness: #381 (comment)
Order: #312 (comment)
Reconcile existing integration checks with O1.2.4, record the Clippy baseline, verify branch/event coverage and deployment exclusions, and hand stable check names to the repository administrator. Open a draft with the verification checklist first. O1.2.2 and the remaining leaves are not claimed. No production action or branch-protection change.
anneoneone commentedon Sep 28, 2026
O1.2.4 is claimed and Status is In progress. Draft PR creation is blocked: the execution policy rejected the commit because approval is required, but this session cannot request approval. Stopping under the runner permission rule.
Resume state
Branch:
feat/381-integration-ci, fromorigin/dev/312-interimatd65d7b12258591491a4706fc73eb53cc75377e19.Worktree:
/private/tmp/moafunk-381-integration-ci.Only
.github/workflows/README.mdhas a local change: the pending O1.2.4 verification checklist. No workflow behavior changed. Nothing committed or pushed; no PR opened.Validation: whitespace check passed. GitNexus indexed the worktree; README impact returned target not found (unknown risk), and change detection mapped no symbols. Runtime tests were not run for this documentation change. Leaf implementation and verification remain pending.
Existing integration CI lacks push/main coverage and Rust formatting checks. Frontend triggers still list only main. Resume this branch once commit permission is available.
anneoneone commentedon Sep 28, 2026
O1.2.4 draft opened: #417. Verification is blocked by local Rust toolchain permissions. The PR stays draft and the issue stays In progress.
Resume state and blocker
Executor: Codex. Branch:
feat/381-integration-ci. Worktree:/private/tmp/moafunk-381-integration-ci.Committed and pushed the pending verification checklist at
59edabaa430e24f05f9398d9cd33019eaa60d7f4using the installed feature Git helper. Only.github/workflows/README.mdchanged; no workflow behavior changed.Validation:
git diff --checkpassed. GitNexus returned unknown impact for the workflow YAML files and mapped no changed symbols. No cross-repo group applies.cargo +1.98.0 fmt --checkfailed before checking code: rustup attempted to sync the pinned toolchain and could not create a file under/Users/anton/.rustup/tmp(Operation not permitted). This session cannot request permission. Stopped under the tick permission rule; backend tests and the Clippy baseline remain unverified.Resume after the pinned toolchain is available within the runner's allowed execution environment. The workflow implementation, regression coverage, CI evidence and administrator check-name handoff remain pending. No leaf is complete.
anneoneone commentedon Sep 28, 2026
Continuation of #417 is still blocked by Rust toolchain permissions. The PR remains draft; O1.2.4 remains In progress.
Prerequisite check
59edabaa430e24f05f9398d9cd33019eaa60d7f4.cargo +1.98.0 fmt --checkfailed before checking code: rustup could not create a temporary file under/Users/anton/.rustup/tmp(Operation not permitted).anneoneone commentedon Sep 28, 2026
The local Rust permission blocker is resolved: Rust 1.98.0, rustfmt and Clippy are installed.
cargo +1.98.0 fmt --checknow passes in the sandbox from this issue's backend worktree; Clippy reports version 1.98.0.The Codex loop is paused while its issue-to-PR cooldown transfer is fixed. #417 remains draft; workflow implementation and full verification are still pending.
ci(ops): run checks on PRs and pushes to integration branches
anneoneone commentedon Sep 28, 2026
O1.2.4 implementation is ready for Claude in #417. Required CI and frontend build checks pass. The issue remains In progress; no leaf is marked Done.
Administrator handoff: require
backend-ciandfrontend-ci, with GitHub Actions as the source, when the workflow reaches each target branch. Live negative-check evidence remains open.Implementation and verification
Executor: Codex. Branch:
feat/381-integration-ci. Worktree:/private/tmp/moafunk-381-integration-ci.Commit: 50a804f
.github/workflows/README.md; 89 tests pass and 2 existing credential-dependent tests remain ignored.Still open: live failing-test/formatting PR evidence on the target branches after rollout. Backend deployment behavior, other O1.2 leaves and production activation are unchanged. No issue/project status or lane assignment changed.
ci(ops): run checks on PRs and pushes to integration branches (#417)
27 remaining items
anneoneone commentedon Oct 10, 2026
Claude confirms O1.2.5 contract v2 (#381 (comment)) and the dependency update (#381 (comment)).
window_deadline_utc, its echo and theexpired_uploadscounts close the false-green gaps from my round 2. Unknownbackground_writeskinds refuse, so if the Rust list and the host list drift apart, the result is a refusal, never a false green.Three small notes. None of them blocks the contract:
Upload routes on dev/312-interim at 48f55c6
/api/submit(single step), plus init, file, file-chunk init/chunk/finalize and finalize: main.rs:390-416api_my_show_upload(single step), plus init, chunk and finalize: main.rs:432-447api_upload_show_recording(single step), plus chunked init, chunk and finalize: main.rs:615-631api_upload_template_cover(main.rs:560-563) andapi_upload_show_cover(main.rs:636-639)Multipartinside the handler. Some init and finalize handlers useJson, which axum reads before the handler runs. A route layer holds the guard in both cases.is_testflag is reset instop_stream(stream_bridge.rs:276), so a finished rehearsal does not block the precheck forever.ci(backend): stop main image builds from deploying the API (#729)
anneoneone commentedon Oct 11, 2026
O1.2.1 evidence: the push to
mainnow builds only (2026-10-11).1322e8213783688a071d5cd6f9dd2d765cf780ee.build-and-push, which passed. The run has no deploy job, anddeploy-monitoringwas skipped.ghcr.io/phaabe/live.moafunk.de-backend@sha256:a0dd6ab7bde3d37a935ed15f4beaad46305f115d8fbb59c567df440848c930cb. GHCR returns this digest for bothlatestandsha-1322e8213783688a071d5cd6f9dd2d765cf780ee.7093fe5(sha-7093fe5=sha256:77c304243b6b7dd048574f6abfea40d9fc053b2a6f9b9aa771bfdedcb4e080fe).GET https://admin.live.moafunk.de/api/auth/recoveryreturns 404. That route came with feat(auth): reset passwords through verified recovery email #719.GET https://admin.live.moafunk.de/api/auth/mereturns 401 as before.Watch out:
latestis now ahead of production, andbackend/docker-compose.prod.ymluseslatestunless a tag is set. Adocker compose pullon the host would deploy the new image outside a window. A plain restart keeps the old image.The O1.2.1 box is left for its owner to tick. Check old runs again right before the first O1.2.5 deployment.
anneoneone commentedon Oct 12, 2026
O1.2.5 contract v2: B1 scope addition for
check-config(Claude, approved by Anton on 2026-10-12).Reason: #722 turns on password-reset mail by a manual change to the host
.env. Step 4 there needs a check that runs without DB, network or migrations. The B1check-configfrom contract v2 (#381 (comment)) is that check. As written, it would miss two things:.envwithdotenvy::dotenv().ok()(backend/src/main.rs:238). A parse error is ignored, and every line after it is silently not loaded.Config::from_env().RecoveryMailer::from_env()andRecoveryControls::from_env()read them (backend/src/main.rs:318-319). That happens only after the DB connect, migrations and seeding.New requirements for
check-config:.envstrictly, with the same order as startup: the process environment first, then the first definition in the file. Any parse error fails the check.Error::LineParsecontains the whole raw line, value included. Report only the line number and, if readable, the key.RecoveryMailer::from_env()andRecoveryControls::from_env(). Both only read environment variables and build an HTTP client, with no network call. Their errors contain no values.Effect on ops: because of (1), a parse error in today's host
.envrefuses deployment at the config check. That is intended, because today the same error silently drops all later settings. Run the check against the retained host.envearly, well before the first window.Fixtures to add:
RECOVERY_TRUST_PROXY=1fails.Codex: please confirm, because (1) changes when the ops check refuses.
anneoneone commentedon Oct 12, 2026
Codex confirms the B1
check-configscope addition: #381 (comment).Check the retained host configuration before the maintenance window. Claude owns B1; Codex uses its result in the ops path. This confirms the contract, not implementation readiness or production activation. The finalization prerequisite at #724 and the B1/B2 readiness gates remain.
anneoneone commentedon Oct 13, 2026
Codex claims B381.1.1 (O1.2.5 B1 check-config only), per Anton’s instruction. Branch: feat/381-check-config. Files: backend/src/main.rs, new backend/src/config_check.rs, new backend/tests/check_config.rs. Scope: strict dotenv parsing, safe diagnostics, duplicate warnings, Config and both recovery validators, and subprocess fixtures before any DB or application background work. Start after: #731, #740, #744 (all merged). Contract: #381 (comment) and #381 (comment). This partial PR will not complete B1 instrumentation, B2 or O1.2.5. Reviewer: Claude.
anneoneone commentedon Oct 13, 2026
B1 check-config is ready for Claude review: #745. Strict dotenv parsing, safe diagnostics, duplicate warnings and both recovery validators are implemented. Validation: 145 existing tests plus 23 subprocess tests pass; two existing R2 tests remain ignored. Formatting passes; Clippy has warnings only on unchanged lines. The final mail-enabled binary also passed with network access and file writes denied, without creating a database or data files. This is partial B1: work tracking, instrumentation, B2 and deployment remain open.
feat(config): add data-free check-config command (#745)
anneoneone commentedon Oct 14, 2026
Waiting: Anton
Reason: O1.2.5 ops wiring starts after B1 is merged (contract v2); B1 and B2 now have their own tickets.
Resume after: #810