Ctrl K
ComunidadGuías…

PLAN.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# PLAN

## Current scope - reject empty retained backup sidecars (#1299) (2026-11-10)

- Read-only finding: `discover_sets` requires an archive plus manifest and
  checksum key, but tests positive size only for the archive. Older backup sets
  with zero-byte `.manifest.json` or `.sha256` objects are counted toward
  `minCompleteSets` without being downloaded; the latest valid backup can
  therefore yield a passing retention-minimum claim with unusable history.
- Fail closed on any listed backup asset (archive, manifest or checksum) whose
  object-store Size is missing, boolean, non-integer or non-positive; retain the
  existing exact-producer prefix and timestamp grouping semantics. No API,
  backup producer, object-store mutation, or output schema changes.
- Add focused evidence that empty or missing-size historical sidecars and
  archives fail before the newest backup is downloaded. Keep a passing
  producer-compatible 3-set fixture and explicit handling for valid sizes.
- Clarify in the off-host operator runbook that all listed retained trios must
  have non-empty objects, **but only newest trio bytes are checksum/manifest
  validated**. Retention count does not prove older archives can restore.
- Validate `python3 -m unittest scripts.qualify_offhost_backup_test`,
  `go test ./scripts -run TestOffhostBackupQualificationPythonSuite -count=1`,
  and `./scripts/verify.sh` when checkout and dependencies permit; otherwise
  report blocked local checks and require exact-head GitHub CI before merge.
- Constraints: no S3 endpoint/credentials available for real scheduled cycles;
  #1299 remains open. No Compose, generated OME, workflows, package, tag,
  deployment or production-host changes; no assertion of live RPO evidence.

## Current scope - bind off-host backup migration ledger to fingerprint (#1299) (2026-11-10)

- Read-only finding: the off-host verifier validates the SHA-256 *shape* of
  `database.migrationFingerprintSha256` but never compares it with the
  migration rows that the PostgreSQL backup producer records from one snapshot.
  An internally inconsistent manifest with recomputed outer checksums passes.
- Reconstruct the producer's ordered `psql -qAt -F '|'` ledger (filename,
  version, checksum, status, release and commit) as UTF-8 lines and require its
  SHA-256 to match the manifest. Refuse missing/malformed ledger fields or
  ambiguous field separators before claiming a complete remote proof.
- Keep this change read-only, producer-compatible, isolated to the off-host
  verifier and its focused tests/runbook. Do not change Compose, backup format,
  restore scripts, automation policy, release identity, or deployed data.
- Regression cases: known producer fingerprint, manipulated fingerprint,
  manipulated migration row with rechecksummed manifest, multiple unordered
  rows, and invalid ledger values. Preserve existing off-host unit coverage.
- Validation: `python3 -m unittest scripts.qualify_offhost_backup_test`,
  `go test ./scripts -run TestOffhostBackupQualificationPythonSuite -count=1`,
  and `./scripts/verify.sh` where repository checkout and dependencies permit;
  GitHub PR CI must independently qualify the exact PR head.
- Limitation: fingerprint consistency is not recovery proof; a fresh isolated
  restore, off-host scheduling and target-host acceptance remain separate
  requirements under #1299.

## Current scope - upload authorization coverage (#1306) (2026-10-10)

- Start from qualified main `0f794710` in the same isolated worktree; carry
  reviewed completion notes only and preserve primary operator changes.
- Read-only audit: upload list/detail/create/delete require channel owner or
  admin; raw media deliberately requires its query capability, not a session.
  Existing tests lack a complete principal/action and capability boundary matrix.
- Add test-only coverage for guest, viewer, unrelated creator, moderator, owner
  and admin across list/detail/JSON-create/file-first multipart-create/delete.
  Require exact denials, token/reference non-disclosure, unchanged denied state
  and source bytes, useful authorized actions and unrelated-upload preservation.
- Separately test all six principals with missing/wrong/another-upload/valid
  media tokens. Even owner/admin must present the capability; a valid bearer
  intentionally works without a session. This is not public VOD access policy.
- Reuse existing real private-storage, upload, actor-context and enqueue helpers.
  Risks: false-positive happy paths, source deletion, orphaned pending files and
  accidental policy drift. Fixtures stay in per-test temporary directories.
- Tests: focused existing upload baseline, new 54-case matrix, full API suite,
  Markdown/whitespace/secret/scorecard guards and literal forced-viewer verifier.
  Require exact-head protected CI, clear reviews and normal squash merge.
- Update security docs with precise handler/capability boundaries. No runtime,
  schema, workflow, deployment contract or candidate/deployed-stack changes;
  legacy-row and candidate-bound wider #1306 acceptance remain open.
- All 54 new cases, full API and literal forced-viewer verifier pass. Fresh
  Compose smoke, 231 Jest tests/four snapshots and fixture teardown pass;
  primary env/OME hashes remain unchanged. Publish four reviewed paths, then
  require protected exact-head CI and clear review state before squash merge.

## Completed scope - reject forged upload storage references (#1306) (2026-10-10)

- PR #1437 fixed the critical Handlebars blocker: protected run `38091283536`
  passes, squash merged as `46a6a42f`. Merge qualified main into the preserved
  #1436 branch without rewriting its published history; resolve only the plan,
  task and changelog conflicts, retaining both scopes and qualification evidence.
- Start from qualified main `65872686` in the reusable isolated worktree;
  preserve primary operator changes and completed #1435 ledger notes.
- Read-only finding: authenticated owners can put `mediaPath`, `mediaToken`,
  `sourceObjectKey` and `sourceObjectURL` in JSON upload metadata. Media GET and
  source deletion then trust those fields, permitting cross-upload access if
  an attacker knows a stored key. Multipart also accepts client metadata.
- Reproduce reading/deleting another creator's local source only in private
  test fixtures. Reject those four server-owned fields on both input formats,
  including case/whitespace variants; preserve custom metadata, external URL
  uploads, normal server-generated source capabilities and idempotent retries.
- Rejected multipart requests must remove their temporary file immediately;
  no upload row, processor enqueue, durable object write or victim mutation.
  Existing stored rows are not silently migrated; document operator review.
- Scope is the API upload boundary plus tests/security documentation, not a
  cross-product refactor. No CI, deployment contract, publication or deployed
  data changes; other #1306 and physical/candidate gates remain open.
- Tests: fail-before/fix-after attack regression, JSON/multipart reserved-key
  and cleanup matrix, ordinary upload/media/delete/idempotency tests, full API
  and literal forced-viewer verifier. Runtime change also requires the actual
  disposable Compose eight-stage product path, report scan and teardown.
- Risks: forged file/object ownership, orphaned pending files, rejected legacy
  integrations, accidental secret disclosure and test-stack collisions. Keep
  all exploit fixtures local; disclose bounded evidence after the fix lands.
- Reproduction confirms both legacy `mediaPath` and `sourceObjectKey` inputs:
  forged JSON accepted (201), raw GET returns victim bytes (200), attacker
  DELETE removes victim file (204). Both regressions fail on qualified main;
  existing upload baseline passes. All data was private temporary test data.
- Publish a small reviewed PR only after local gates pass, then require exact
  head protected CI, clear review threads and squash merge before roadmap notes.
- Correct the existing VOD current-state document against inspected code:
  multipart size/type validation, optional object storage, bounded transient
  retries, unpublished recording linkage, manual UI refresh/publish and source
  cleanup. Distinguish listing visibility from media/object access, and explain
  in-process cleanup timers and existing-row review. No runtime expansion.
- Requalify focused/full API tests and Markdown links, then the actual eight-stage
  product path and literal verifier on the integrated source. Preserve earlier
  reports, scan the new report, remove owned fixtures and require refreshed CI.
- Integrated-source qualification passes: all eight product stages in 25.765s,
  secret-scanned report `34acaec5...0924014`, plus literal forced-viewer verifier.
  Both owned projects/volumes/env removed; primary env/OME hashes unchanged.
  Reviewed seven-path PR #1436 passes protected exact-head `11cd4d71` run
  `38092639377`, including Merge gate; squash merged as `0f794710` with clear
  reviews and fresh base. Candidate and wider release acceptance stay separate.

## Completed scope - Handlebars tooling security prerequisite (2026-10-09)

- PR #1437 merged as `46a6a42f` after local verification and protected CI
  `38091283536`; final Windows/Linux matrices pass 68 cases/three skips with
  zero retries. #1436 resumes under its own API qualification tasks above.

- PR #1436 is preserved at remote `7ae0ff4e`, with local checkpoint `1b2bd00d`.
  Its protected run `37930944368` passes product/build checks but fails npm audit.
  Start this separate dependency fix from main `65872686`; restore/requalify the
  upload branch after this prerequisite merges. Preserve primary operator edits.
- Official GHSA-xw65-4hp5-5hc7, GHSA-8r5x-fm3f-whwj and GHSA-p8wg-vrv2-v86f
  published October 8 affect Handlebars through 4.7.9; all identify 4.7.10 as
  patched. Local npm audit reproduces one critical plus 20 moderate findings.
  `npm explain` places Handlebars under development dependency ts-jest 29.4.14.
- Pin the patched transitive dependency, regenerate the portable lock in clean
  Node 24 Alpine, extend the existing nested-copy security guard and document
  the advisory/source/candidate boundary. No application API or player changes.
- Risks: test transformer/compiler behavior, optional lock entries and package
  provenance. Keep all existing audit thresholds, CI jobs and release rules.
- Tests: focused lock/security guards, clean Windows/Alpine installs and audits,
  full viewer integration/build/Playwright, Docker viewer build and literal
  verifier. Require protected CI on the proposed commit before squash merge.
- This does not alter published RC23, authorize a new candidate, or complete
  broader #1306 acceptance. VOD documentation cleanup follows these blockers.
- Qualification investigation: preserve the first Windows browser reports,
  trace the two stalled WebKit cases in isolation, then assess a full matrix
  without changing existing deadlines, retries or capability assertions.
- The Windows matrix exposed a separate deterministic fixture race: creator
  setup changes its mocked stream to live after request counts, while the real
  page polls every four seconds. A slow run skips the asserted offline state.
  Keep the fixture offline through repeated refreshes, then explicitly publish
  its live response before the existing transition assertion. Verify fresh API
  requests and both UI states; no runtime or polling-policy changes.
- October 10 resume: host reboot ended the verifier before a final result was
  captured and Docker Desktop is stopped. Restore the test prerequisite, retain
  earlier failed browser reports, rerun final Windows qualification after the
  fixture repair, and capture the literal verifier in a retained local log.

## Current scope - channel authorization and real product proof (#1306) (2026-10-07)

- Complete: 48 API cases, eight real product stages, forced-viewer verifier and
  protected run `37716919903` pass; #1435 squash merged as `65872686`. #1299
  reopened with its missing remote scheduler proof documented; no release gate
  was closed by inference. No CI/contract/publication/deployment changes.
- Start from protected main `99763c41` in the reusable isolated worktree. Keep
  completed #1433 ledger updates and all primary storage/env/OME edits intact.
- Add a table-driven sensitive-channel action matrix over the existing handler,
  real test repository and shared helpers: guest, viewer, unrelated creator,
  moderator, owning creator and admin. Cover private owner listing, detail/key
  disclosure, public playback, metadata mutation, delete, start/stop and rotate.
- Denials must preserve channel/session state and never disclose the private
  stream key. Positive owner/admin cases must prove the action works, and an
  unrelated channel must remain unchanged. No role-policy change is intended.
- Document exactly this handler-level matrix and its limitations; existing
  session tests and a real-stack eight-stage product exercise remain separate.
  Do not claim complete DAST, CSRF/MFA, upload/flood or physical-device proof.
- Run focused API tests, the actual RTMP/1080p decode/chat/moderation/VOD golden
  path through a disposable canonical Compose stack, and literal verifier.
  Preserve failed attempts, secret-scan retained reports, verify teardown and
  primary hashes, then publish a small PR with protected exact-head qualification.
- Risks: privilege escalation/IDOR, accidental credential exposure, destructive
  test actions, fixture isolation and media deadlines. All mutations target
  private per-test storage or our owned disposable stack, never an operator.
- No CI/workflow, required-check, deployment-contract, stable or publication
  changes. #1432/#1434 and fresh candidate publication remain approval-gated;
  RC23 is unchanged and not approved by source-only security evidence.
- Roadmap audit found #1299 closed two seconds after #1419 merged, although
  the later completion comment explicitly says OPEN and lists missing scheduled
  remote provenance/failure/RPO evidence. No later acceptance exists. After
  this qualification slice, restore the tracker to open with the exact evidence
  links; do not treat a verifier implementation as external scheduler proof.

## Current scope - qualify viewer tooling PR #1433 (2026-10-07)

- Complete: all local gates and protected exact-head run `37631816758` pass.
  PR #1433 squash merged as `99763c41`; no candidate/stable/deployment mutation.
- Independently qualify @types/node 26.6.4 and ts-jest 29.4.14 while #1432
  awaits separate CI-guard approval. Preserve that branch and primary edits.
- Exact bot head `7e939fed` fails the Node-types baseline and optional WASM
  lock guard; both Alpine image jobs independently reject the incomplete lock.
  Keep those checks intact and regenerate through clean Node 24 Alpine.
- Upstream ts-jest patch reverts compiler utility changes. Risks: transformed
  test behavior, type regressions, cross-platform optional dependencies and
  newly reported vulnerabilities. No runtime feature or CI policy changes.
- Audit now blocks Sharp <0.35.5 (GHSA-wq5f-xc86-pv6w) and source-map-js <1.2.2
  (GHSA-68fv-2mgg-jv7q). Extend this viewer dependency slice to their published
  patches and lock/version regressions before qualification. Retain high/
  critical gate; report remaining moderate tooling findings, not zero audit.
  RC23 still contains old pins; source fixes cannot qualify its immutable root.
- Update the reviewed baseline, explicitly guard the ts-jest pin, document
  tooling qualification, then clean Windows/Alpine install and audit, focused
  Go tests, full viewer integration/matrix, Docker build and literal verifier.
- Publish qualification to the existing PR with a strict, accurate scorecard;
  require exact-head protected CI, clear reviews and squash merge. No tag,
  stable promotion, contract change or operator deployment in this scope.
- Windows eight-worker matrix had one page crash; serial run passes that test
  but times out Firefox failed-playlist state (67 passes/three skips each).
  Linux full matrix passes 68/three. Inspect isolated page/network/console and
  focused reproduction before claiming qualification or changing fixtures.
  Preserve failures, zero retries, engine coverage and current deadline.
- Final Windows run exposes a proven retry assertion race: iPhone/WebKit
  correctly renders unavailable after metadata recovery, but the test requires
  transient video visibility. Direct probe confirms no MSE, ManagedMediaSource
  or native HLS. Reuse one capability-aware source-outcome assertion for initial
  attachment and retry; require fresh retry request/error clearance. Keep all
  supported engines' attachment assertions, strict fallback capability checks,
  projects, deadlines and zero retries. Update compatibility docs/guard, then
  rerun focused affected tests, both full matrices and literal verifier.

## Current scope - prepare and publish successor prerelease (2026-10-04)

- User approved a prerelease from qualified main, not stable promotion,
  latest aliases or deployed-stack mutation. #1426 merged as `95d309fd` after
  protected exact-head run `37221518727`; duplicate #1425/#1427 are closed.
- Observed latest tag is rc.22; rc.23 is unused. Recheck immediately before
  pushing a new immutable tag. Never move or reuse a failed candidate tag.
- Refresh stable draft/changelog and stale release index/reference wording to
  describe RC22 as historical/rejected, current patched source, and successor
  preparation honestly. Follow existing note naming: published snapshots only
  after publication. No runtime, workflow or deployment-contract edits.
- Require named full source smoke gate plus already passed eight-stage source
  product gate, focused docs/release checks and literal verifier; publish a
  small documentation PR and squash only with protected exact-head CI.
- The source gate fixture uses saved production env and an inline development
  override. Its first two setup attempts failed mode normalization, then Docker
  dependency download with inherited offline settings. Use the existing
  network-enabled production build proxy/checksum settings for this explicit
  gate invocation only; verifier defaults and validation remain unchanged.
- Publish rc.23 only from qualified main, watch the real release workflow,
  then verify public checksums, signed manifest, exact images and sanitized
  pull-only product evidence. Prepare human notes without creating an early
  placeholder release. Publication failure blocks completion; no exceptions.
- Risks: current vulnerability findings, release-only action changes, packaging
  and registry access. Eight external candidate-bound gates remain open until
  actual target evidence passes; source/mock/CI evidence cannot close them.
- Preserve primary dirty checkout, env and generated OME. Own test credentials,
  diagnostics and disposable stack stay under ignored artifacts; only scanner-
  approved evidence is shared. Stable/channel promotion is out of scope.

## Current scope - qualify Docker action updates #1425-#1427 (2026-10-03)

- User explicitly approved qualification and merge of these CI updates. Combine
  the three pin-only changes into #1426, based on current main after #1430.
  Close #1425/#1427 as superseded only after the combined change merges.
- Official upstream tags resolve to QEMU 4.4.0 `99012661954931238ded8c8b007157a8430204e1`,
  Buildx 4.4.1 `f87e5991a6d7451dcb8d9637bfbc97413f497069`, and build-push
  7.4.0 `c3c9e263c25d99ce0380d002d59b67737d91b0dc`. Upstream action.yml
  comparison shows no input/output interface changes. Build-push includes
  workflow-command injection protection in metadata logging.
- Preserve workflow triggers, permissions, inputs, signing, and immutable
  promotion. No deployment contract, gate weakening, registry publication,
  release/tag creation, or production promotion belongs in this change.
- Risks: Buildx cloud/pre-pull behavior and shared toolkit changes. Add a
  cross-workflow immutable pin guard; run existing release/promotion/CI contract
  tests and literal verifier, then exact-head protected CI and review checks.
- PR image jobs exercise Buildx directly. QEMU/build-push remain release-only:
  source/pin/interface review and contract tests do not prove a new candidate
  publication. Keep that acceptance boundary explicit in docs and roadmap.
- #1430 merged as `e0cda8d9`, protected run `37104440225` passed. Preserve
  unrelated primary-checkout edits and use the existing isolated worktree.
- Follow-up authorization (2026-10-04): user approved a new prerelease from
  qualified main, not stable promotion or a deployed-stack change. Before
  finishing qualification, run the existing source production golden path
  using its disposable env/state and scanner-approved report. Do not treat
  source media success as candidate or clean-host evidence. The next unused
  observed candidate is rc.23; recheck tag availability before publication.
- Fresh run `37218822030` passes the scorecard but fails iPhone/WebKit source
  attachment. The test mocks playback metadata but leaves its example.com HLS
  URL uncontrolled; distinguish missing engine capability from fatal transport
  teardown using a matching Linux Playwright container before changing tests.
  If the fixture races attachment with network failure, control that transport
  in attachment tests and add explicit failure coverage. Keep real HLS code,
  supported-engine assertions, zero retries, and product decode gates intact.
- Controlled transport also exposes the missing-source recovery fixture's
  request-count race: auth resolution legitimately refreshes playback, and the
  mock returns healthy metadata on every request after the first. Keep it
  unavailable until the test explicitly clicks retry; require a new request
  after that click. Change test state only, not the production auth refresh.
- Supplemental candidate preflight while protected CI runs: execute the named
  full release smoke gate against this source using generated disposable
  development/build credentials. Back up/restore the isolated generated OME
  file, tear down only this owned Compose project, remove the test env, and
  confirm primary env/OME hashes unchanged. Retain scanner-approved report
  JSON only; raw diagnostics and synthetic credentials stay local. This does
  not publish a candidate or alter the deployment contract.

## Current scope - qualify viewer runtime PR #1430 (2026-10-03)

- #1429 merged as `4aa9980d`; #1423 is superseded/closed. Dependabot replaced
  #1424 with #1430 after the merge. Use this fresh branch, not the deleted one.
- The new update retains Next/ESLint 16.3.8, brace-expansion 5.0.12, and the
  guarded braces fork. It changes HLS 1.7.2 to 1.7.3 and React/React DOM plus
  their types to 19.3.0. Align baseline assertions and supported-line docs only.
- Upstream HLS patch notes include live playlist reload recursion and selection
  fixes: https://github.com/video-dev/hls.js/releases/tag/v1.7.3. React 19.3
  includes renderer changes: https://github.com/react/react/releases/tag/v19.3.0.
  Do not adopt new APIs or redesign the viewer as part of qualification.
- Risks: media loader regressions, React rendering/hydration/async changes,
  updated types, and cross-platform optional lock entries. Existing mocked
  media routes cannot establish real decoder/device compatibility.
- Run focused Go baseline, clean install/audit, complete viewer integration,
  Alpine image build and literal verifier. Monitor disk; do not prune shared
  Docker data. Publish to #1430 only after local gates pass; require protected
  exact-head CI, clear review threads, and squash merge. Record bounded roadmap
  evidence and completed #1429 ledger without closing broader release issues.
- The refreshed bot lock again omits optional WASM dependencies required by
  Alpine/npm 11.16 while Windows/npm 11.6.2 accepts it. Add a structural lock
  regression test for the current Sharp/NAPI WASM consumers, demonstrate the
  missing entries, then regenerate in clean Alpine. Document cross-platform
  qualification; `npm ci` remains the version/platform validity authority.

## Current scope - qualify viewer tooling PR #1429 (2026-10-02)

- Qualification found new blocking advisories in Next.js, brace-expansion,
  and the ESLint-only braces dependency. Extend this viewer dependency PR to
  fix these before publication: Next/ESLint 16.3.8, brace-expansion 5.0.12,
  and a small MIT-licensed local braces 3.0.3 fork with explicit nesting/AST
  guards. Upstream braces has no patched release; do not waive its advisory
  or disguise an unpatched npm version.
- Preserve upstream source/license and provenance in the fork, cap syntax
  nesting before parser-internal recursion and validate external ASTs before
  recursive walkers. Test attacks and normal glob behavior, install the fork
  through a local npm override, and require a clean audit. Review removal when
  upstream ships a fix. This adds no CI exception or workflow change.
- Finish the current Dependabot tooling update; #1422 was closed and replaced
  by #1429. Preserve the unpublished #1422 branch for recovery.
- CI originally fails four exact package-version assertions, before viewer
  CI runs. Align Node types, ESLint config, Jest, and jsdom environment pins
  with tooling updates, plus Next's security patch pin. Keep all other runtime
  and deployment assertions intact.
- Risks: changed Jest/ts-jest behavior, TypeScript definitions, ESLint rules,
  and transitive dependencies can expose real regressions. A passing version
  guard alone is insufficient evidence.
- Run the focused Go baseline test, clean npm install/audit, full viewer
  integration, and required verifier including Docker smoke. Monitor free disk
  space because the prior build filled C:. Then publish to the existing PR,
  require protected CI and review resolution, and squash merge.
- Qualification also exposed stale documentation assertions in the dependency
  policy test. Refresh its version/review-date expectations with the documented
  current overrides and fork; preserve all fail-closed exception-policy checks.
- Windows npm reconciliation pruned optional WASM lock entries required by
  Alpine npm. Regenerate using the existing Node 24 Alpine builder with a clean
  dependency directory, then prove clean Windows and Docker installs. Do not
  hand-invent lock integrity or weaken `npm ci`.
- #1420 is merged as `5e4d3b8f`. Keep #1307 open for actual media/device proof;
  the roadmap remains focused on the first stable operator release (#1293).
  Reconcile the stale #1420 completion ledger with verified GitHub state and
  post bounded browser/security evidence to the relevant open roadmap issues.

## Current scope - finish browser matrix review and CI (#1420) (2026-09-25)

- Resolve the two open review findings on the existing #1307 browser matrix PR.
  Keep supported Chromium/WebKit/mobile projects accountable for HLS source
  attachment; allow Firefox's documented capability-gated unavailable result.
- Update the existing Player unit tests to model an HLS-capable browser for
  source recovery and delayed media errors. Add a separate unsupported-capability
  assertion so the Firefox fallback remains covered rather than hidden by the
  mock change.
- Preserve the declared support and deployment boundaries. Qualify focused
  Jest and browser behavior first, then run strict lint, production build,
  the six Playwright projects, literal `./scripts/verify.sh`, protected CI,
  review-thread resolution, and squash merge. Keep #1307 open for its remaining
  real-media engine and physical-device acceptance evidence.

## Current scope - viewer compatibility matrix (#1307) (2026-09-12)

- Turn the existing single-project Playwright suite into an explicit browser/device qualification contract for the supported viewer without changing product behavior merely to make tests pass.
- Preserve one full Chromium desktop regression project as the broad baseline, then add a smaller release-critical matrix across Chromium, Firefox, WebKit, Android/Chrome emulation, and iPhone/WebKit emulation. Reuse existing channel/chat/playback, auth/navigation, creator setup, mobile overflow, and accessibility tests rather than building a parallel fake viewer.
- Separate engine/device support from protocol support. Mocked browser tests can prove layout, navigation, auth/cookie behavior, chat UI/reconnect presentation, player fallback/error states, and creator flows; real HLS/LL-HLS/WebRTC decode evidence remains owned by the production golden path and must not be inferred from mocked routes.
- Keep retry policy honest: do not add broad retries to turn browser-specific failures green. Any unsupported engine behavior must be fixed, narrowly excluded with documented rationale, or surfaced as a release blocker.
- Publish a support matrix that names the tested desktop/mobile projects, which critical flows run on each, browser-version policy, media/protocol caveats, and the boundary between CI emulation and real device/Safari evidence.
- Keep the release feature freeze intact: this slice may make compatibility fixes required by the declared baseline, but it does not add 4K support, new media protocols, or unrelated viewer features.

### Risks and test boundaries

- Running every existing browser test five times would be expensive and can obscure engine-specific failures behind unrelated coverage. Preserve the current complete suite on Chromium and define one focused compatibility spec set that runs on all five projects.
- Playwright's mobile presets can change user agent, viewport, touch, and device scale behavior. Ensure project-level emulation does not fight tests that intentionally resize through several narrow widths; mobile critical coverage should assert touch-capable behavior and no document overflow without hard-coding one device-only layout.
- HLS support differs by browser engine. Tests must distinguish native HLS, hls.js fallback, and unsupported/mock-only behavior instead of pretending a successful mocked playlist proves decoder support.
- WebKit on Linux is Safari-equivalent engine coverage, not a claim that current macOS/iOS Safari hardware was exercised. Documentation must state that boundary explicitly.
- The permanent CI path should install only browsers actually required by the matrix and retain one production standalone build/server boundary. Avoid duplicate Next builds per project.

### Test and rollout plan

- Add named Playwright projects for Chromium desktop, Firefox desktop, WebKit desktop, Android/Chrome emulation, and iPhone/WebKit emulation using Playwright's maintained device descriptors where appropriate.
- Add/retag a compact critical compatibility suite covering channel watch/playback recovery presentation, authentication/navigation, chat, creator setup, accessibility, mobile/touch interaction, and horizontal-overflow refusal.
- Add static repository contract tests for the project names, browser engines/device presets, no blanket retry policy, and expected critical/full-suite split.
- Update viewer/testing/operator documentation with the support matrix, browser-version policy, protocol caveats, and real-media follow-up requirement.
- Run clean npm install, zero-high audit, strict lint, Jest, all critical projects, full Chromium integration, production build, literal `./scripts/verify.sh --viewer`, protected viewer CI, automated review, and the aggregate Merge gate before squash merge.
- Keep #1307 open if real-media engine-class evidence or real Safari/device evidence remains unproven after this repository-side matrix lands.


## Current scope - scheduled off-host backup freshness qualification (#1299) (2026-09-12)

- Finish #1299's remaining repository-solvable scheduled/off-host slice by qualifying the existing `bitriver.postgres-backup/v1` objects emitted by `scripts/backup-postgres.sh`; do not add a second producer, scheduler, or remote-storage format.
- Add one read-only S3-compatible verifier that fails closed on incomplete remote trios, stale newest evidence, insufficient retained complete sets, checksum/manifest mismatch, canonical manifest-shape violations, or a backup that does not match the exact candidate release and full source commit.
- Mirror the restore-side v1 manifest contract: archive identity/size/hash, database/server identity, non-empty applied migration entries with checksums, migration fingerprint, numeric non-empty row counts, pg_dump/psql identity, and exported-snapshot/exact-row-count consistency are all release evidence.
- Preserve the producer's exact `${UPLOAD_PREFIX}/${BACKUP_BASENAME}` key construction, including configured leading/trailing separators. The verifier must not normalize a prefix into a namespace the producer did not write.
- Keep scheduler provenance separate from object validity. A manual producer run creates the same trio, so the verifier may prove remote completeness, integrity, freshness, retention, and candidate identity but must not claim scheduled provenance without separate scheduler logs.
- Credentials stay in the AWS credential chain. Provider stderr and command arguments are not retained in errors/reports; the verifier performs only list and download operations and never mutates remote storage.

### Risks and evidence boundaries

- A fresh checksum-valid trio is insufficient if canonical restore would reject its manifest or if it belongs to another release. Require the full existing manifest contract and mandatory candidate identity before `status=pass`.
- Remote object retention is operator/storage policy; the repository verifier proves only a configured minimum complete-set count and does not prune S3.
- A passing report is not restore proof, scheduler provenance, provider durability-SLA evidence, alert-delivery evidence, host-state recovery, or stable promotion. Existing recovery evidence remains separate.
- Production-like completion still requires a real scheduled endpoint: multiple scheduled cycles, visible non-zero remote-failure behavior, local-copy-loss re-verification from remote bytes, retained scheduler provenance, and the resulting secret-safe report. Repository tests cannot manufacture that proof.
- No Compose, root `.env`, generated OME config, permanent CI workflow, backup producer, restore implementation, or remote object mutation belongs in this slice.

### Test and rollout plan

- Deterministic Python tests cover happy path, RPO staleness, incomplete sets, retention minimum, checksum corruption, complete canonical manifest shape, migration/row-count/tool/consistency refusal, exact candidate identity, producer prefix-separator parity, atomic reporting, and secret-safe AWS errors.
- A Go wrapper runs the Python suite through normal Linux/macOS/Windows repository Go gates without adding a permanent workflow.
- Run docs consistency and literal `./scripts/verify.sh` through protected CI, resolve automated review, require the exact-head aggregate Merge gate, and squash merge only after every review conversation is resolved.
- Keep #1299 open after the repository tool lands until live production-like scheduled/off-host evidence is executed and retained against an actual remote endpoint.

### Process note

- Implementation began before this PLAN/TASKS record because the active connector initially exposed only whole-file replacement for these very large ledgers. Automated review correctly flagged the workflow violation. The ledgers are amended before merge, and no exception to the required workflow is claimed for future scoped changes.


## Current scope - qualify viewer tooling updates (#1413) (2026-09-04)

- Qualify the refreshed Dependabot tooling group on current `main`:
  `@testing-library/react 16.3.3`, `@testing-library/user-event 14.6.6`,
  `@types/node 26.4.0`, `@types/react-dom 19.2.5`,
  `eslint-config-next 16.3.4`, `jest 30.5.1`, and
  `jest-environment-jsdom 30.5.1`, together with their exact lock graph.
- Keep this slice development-only. Preserve Node 24/npm 11, Next.js 16.3.3,
  React 19.2.8, shipped viewer behavior, APIs, deployment contracts, CI
  workflows, and the separate ESLint 10 major in #1404. Align the checked
  dependency invariant without changing user-facing or operator documentation.
- Treat Jest 30.5 as the primary compatibility risk because it changes resolver,
  runtime, haste-map, and filesystem-watcher behavior. Cover it with all 26
  typed Jest suites and stable snapshot counts; cover Testing Library event and
  focus semantics with the existing interaction suite, and type/config changes
  through strict lint, TypeScript, and a production build.
- Keep repeated local qualification clean after a production build by making
  the existing generated `.next` module-path exclusion separator-independent.
  Require a regression check for that portable ignore and a post-build Jest
  rerun with no standalone-package haste collision.
- Review the two new transitive install scripts rather than leaving npm's
  advisory unresolved. Deny `@parcel/watcher` and `unrs-resolver` lifecycle
  scripts explicitly: their exact platform binding packages are present in the
  lock graph, while the scripts only build from source or fetch a missing
  fallback. Prove the denial with a fresh Node 24 container install/build and
  the protected native-arm64 gate.
- Require a clean install, exact direct versions, zero-vulnerability audit,
  focused invariant checks, Playwright integration, literal
  `./scripts/verify.sh`, protected cross-platform CI, native arm64 and
  production image scans, review, and squash merge. Record #1406's verified
  merge before advancing these tasks.

## Current scope - qualify viewer runtime updates (#1406) (2026-09-04)

- Qualify the focused Dependabot runtime group on the clean viewer-verification
  baseline: `hls.js 1.7.1`, `next 16.3.3`, and `ovenplayer 0.10.54`. Treat the
  Next.js patch as security-relevant because its release fixes unauthenticated
  RCE advisories; do not split it away from the already-generated exact lockfile.
- Preserve React, viewer tooling, temporary fixed overrides, shipped routes,
  player selection/fallback behavior, APIs, and the deployment contract. Align
  the checked runtime invariant and dependency-policy disposition with the new
  direct versions; do not absorb the separate tooling or ESLint major PRs.
- Cover the hls.js minor-version type narrowing and OvenPlayer caption/subtitle
  behavior through strict lint, the existing typed unit suite, production build,
  and Playwright integration. Require a clean install, exact resolved versions,
  zero-vulnerability audit, and focused runtime/policy checks before the full gate.
- Require literal `./scripts/verify.sh`, protected cross-platform Go/quickstart,
  viewer integration/build/audit, native arm64, production image scan, review,
  and squash merge. Update the preceding viewer task only with verified merged
  evidence, and keep the broader browser/device roadmap issue #1307 open.

## Current scope - clear viewer verification blockers (2026-09-04)

- Preserve the intended Jest unit boundary while making discovery portable in
  nested Windows worktrees: replace the absolute `<rootDir>` `testMatch` with
  the equivalent root-independent `**/__tests__` glob. Do not use Jest's broad
  defaults, which also collect the separate Playwright `tests/*.spec.ts` files.
- Remove newly published npm audit findings without suppressions. Pin current
  fixed `browserslist 4.28.9` and `postcss 8.5.28`, refresh the exact lockfile,
  align the PostCSS invariant, and update the dependency-policy disposition so
  maintainers know which temporary overrides remain release inputs.
- Keep shipped UI behavior, APIs, deployment contracts, CI workflows, and
  operator configuration unchanged. Record the Windows discovery result as a
  bounded #1307 finding, not closure of its browser/device compatibility scope.
- Require clean install, exact resolved graph, zero-high npm audit, strict lint,
  all 26 Jest suites without Playwright-spec collection, production build,
  focused baseline test, literal `./scripts/verify.sh`, protected cross-platform
  CI, viewer integration, native arm64, production image scan, and review before
  squash merge. Refresh #1406 only after this prerequisite is on `main`.

## Current scope - clear critical x/crypto release finding (#1306) (2026-09-03)

- Treat the blocking image-scan finding as a release-critical dependency update:
  production binaries currently resolve `golang.org/x/crypto v0.54.0`, and
  Trivy reports fixed critical `CVE-2026-56854` with `v0.55.0` as the patched
  version. Do not suppress or accept the finding for stable promotion.
- Update the canonical module requirement and checksums to `v0.55.0`. Preserve
  the checked-in offline replacement, which contains only the locally required
  `pbkdf2` implementation and does not vendor the affected SSH package; verify
  production-module generation removes the replacement and resolves the exact
  patched upstream graph.
- Keep this slice dependency-only: do not change application behavior,
  deployment contracts, CI workflows, scanner thresholds, or operator secrets.
  Record the security-relevant dependency change in the v1.2.3 draft notes.
- Verify checksum/module consistency, offline Go tests, production-module
  download/build identity, repository secret/diff hygiene, and literal
  `./scripts/verify.sh`. Require protected PR CI including the blocking image
  scan before squash merge, then refresh the blocked dependency PRs against the
  patched `main` rather than rerunning their stale vulnerable commits.

## Current scope - recovered immutable stack production golden path (#1299) (2026-08-22)

- Extend the merged published-package lost-host rehearsal with an explicit
  recovered-runtime phase. Start from the exact verified RC21 launcher and its
  encrypted restored host state, then boot the installed
  `/opt/bitriver-live` Compose bundle with pull-only release-set-pinned images;
  do not substitute a checkout deployment tree or rebuild product images.
- Reuse the existing `bitriver.production-golden-path/v1` client unchanged for
  the public product assertions: readiness and authenticated aggregate status,
  account/channel creation, deterministic 1080p RTMP, advancing and decodable
  OME/transcoder playback, offline transition, chat/moderation, VOD upload and
  playback, and final aggregate health.
- Preserve and bind the recovery result rather than running an unrelated clean
  stack. Restore the exact manifest/checksum-bound Postgres trio into a fresh
  database on the recovered Compose volume, retain the recovered encrypted
  configuration and local/external object fixtures, prove representative rows
  exist before the golden path, and prove those rows plus golden-path-created
  state remain afterward.
- Publish one secret-scanned completion report that binds the existing
  `bitriver.disaster-recovery/v1` report, exact release-set/package identity,
  observed runtime image references, restored-state invariants, and golden-path
  report SHA-256. Remove only the recovered-golden-path item from
  `remainingAcceptance`; keep scheduled/off-host freshness and RPO evidence
  explicit for the final #1299 slice.

### Execution and isolation

- Add a dedicated wrapper instead of changing the default local/staged recovery
  command. It must require the exact prerelease tag, full commit, and a fresh
  evidence directory, download and verify the public launcher first, and use
  only generated temporary host/config/database roots.
- Prepare a production-shaped private environment from the package's
  `deploy/.env.example` plus the exact release-set image/dependency digests
  before the source snapshot. The encrypted restore may change only installer-
  managed host path/UID/GID values; relocate those to the predicted final root
  and require every other byte to match. Private values stay in temporary
  sentinel files and may not enter retained reports or command output.
- Refuse canonical-container collisions before Docker mutation. Use a unique
  Compose project and project-owned Postgres/Redis/transcoder volumes, bounded
  service/HTTP waits, `--no-build --pull never` after exact pulls, and
  unconditional container/volume/temp-root cleanup on success, failure, or
  interruption.
- The repository-side harness may prepare fixtures, inspect Docker runtime
  identity, and drive the existing golden-path client. Recovery and runtime
  evidence must still identify the verified public package and its installed
  source-free bundle; repository code is not part of the recovered runtime.

### Risks and evidence boundaries

- The canonical Compose file has fixed container names, so parallel execution
  is unsafe even with a unique project. Fail closed when any canonical name is
  present and never stop or adopt an operator container.
- A fresh Compose Postgres container initially creates its configured database.
  Restore the verified trio into a different fresh database before application
  startup, switch the recovered environment to that database, recreate the
  Postgres container without deleting its project volume, then run canonical
  migrations and applications. Refuse an existing restore target or a database
  identity/hash mismatch before API startup.
- Automated review hardening must keep that recovered Postgres container
  unmodified. Do not install packages or copy backup/restore tooling into
  either exercised Postgres container, including the original fresh-database
  restore inside `test-disaster-recovery.sh`. Make the packaged backup and
  restore scripts compatible with the exact release-set Postgres image's
  `/bin/sh`, run both restore phases in disposable read-only helpers using the
  database containers' network namespaces and read-only script/input mounts,
  and bind both original and runtime restore-helper image references to the
  release-set identity in final evidence.
- Windows filesystem compatibility shims may emulate package-install symlinks,
  but they do not replace RC21's already-passed Ubuntu package/symlink/systemd
  evidence. This slice proves the recovered exact-image Compose product path;
  physical XOA/Nginx Proxy Manager/browser/reboot evidence remains #1297.
- Exact-head review hardening must also keep Linux path preparation valid before
  recovery creates the future `/etc/bitriver-live` subtree. Canonicalize the
  already-existing disposable recovered root, then append the managed relative
  config path; do not call `realpath` on nonexistent parent components.
- Do not change `deploy/docker-compose.yml`, root `.env`, generated OME
  expectations, CI workflows, the release workflow, or the default verification
  gate. Do not claim scheduled backup freshness, off-host durability, alert
  delivery, or a stable release from this result.

### Test and rollout plan

- Add focused tests for release-set-to-environment/runtime binding, original-
  disaster-to-runtime Postgres archive binding, completion-report schema/
  status/hash/invariant checks, exact original/runtime restore-helper image
  refusal, secret refusal, unrelated golden report refusal, and honest
  remaining acceptance.
- Run both Bash and the exact Postgres Alpine image's `/bin/sh` syntax/runtime
  paths for the backup/restore scripts, and assert the recovered-stack wrapper
  and its nested lost-host drill contain no package installation or
  in-container script injection. Add a source regression for Linux-safe future
  config-root construction.
- Run the existing staged and published lost-host rehearsals to guard backward
  compatibility. RC21's first recovered-stack exercise reached the real
  Postgres restore and Dockerized production golden path but is not promotable
  evidence because review found that it modified the runtime Postgres
  container. Merge and publish the portable-helper fix, then rerun the wrapper
  against the exact next public candidate; scan retained evidence and verify
  complete teardown.
- Run Python compilation, Bash syntax, pinned ShellCheck, Markdown links,
  `git diff --check`, and literal `./scripts/verify.sh`. Publish a focused PR,
  resolve automated review, require protected exact-head CI, squash merge, and
  post a bounded #1299 handoff before starting scheduled/off-host RPO work.

### Immutable candidate execution result

- PR #1401 merged the reviewed portable-helper implementation to protected
  `main` as `efca191c`. Annotated tag `v1.2.3-rc.22` points to that exact commit,
  and its complete release workflow passed before qualification.
- Independently downloaded `release-set.json` has SHA-256 `938b22cc...` and
  names the exact tag, commit, repository, five first-party images, and eight
  dependency digests. The no-checkout Ubuntu clean-host workflow passed against
  the same tag/hash.
- `test-recovered-stack-golden-path.sh` passed against the exact public RC22
  release set/package. Secret-scanned completion evidence binds both Postgres
  helper identities and all 13 observed services to the release set; cleanup
  left no rehearsal containers or volumes.
- #1299 now records the bounded release, clean-host, and recovered-stack result.
  Only production-like scheduled/off-host backup freshness and RPO evidence
  remains open; no stable-promotion or off-host-durability claim is inferred.

## Current scope - published-package lost-host recovery qualification (#1299) (2026-08-21)

- Extend the existing disposable lost-host rehearsal so it can consume an
  exact public Linux launcher archive instead of always staging release assets
  from the checkout. Bind the retained `bitriver.disaster-recovery/v1` report
  to the supplied `bitriver.release-set/v1` bytes, candidate tag, full source
  commit, package filename, byte length, and SHA-256.
- Execute the backup, encrypted host-state restore, Postgres fresh-database
  restore, external-object inventory, and recovered packaged-host install with
  the scripts extracted from that verified package. Keep the repository test
  driver and a fixture launcher binary outside the evidence claim so the drill
  remains portable on Windows while proving the shipped recovery-script bytes.
- Add a public-download wrapper for the Linux amd64 launcher package and
  release-set manifest. It must validate identity and package checksum before
  extraction, reject unsafe archive member paths, use a fresh temporary root,
  retain only sanitized reports plus the public manifest when requested, and
  clean all disposable hosts/containers on success, failure, or interruption.
- Preserve the current build-mode rehearsal as the default. Published mode is
  explicit and may claim only release-set/package binding plus recovery-script
  execution; Sigstore verification, Linux binary/package acceptance, the
  recovered production golden path, and scheduled off-host freshness remain
  separately owned evidence.

### Roadmap ordering and evidence boundary

- RC20 predates the packaged encrypted recovery payload. RC21 is the first
  immutable candidate from current `main` that can satisfy #1299's exact
  published-package recovery boundary; its release workflow must finish before
  the live published-mode drill can pass.
- This slice closes only the first of #1299's three recorded remaining items.
  Keep #1299 open for the production golden path on the recovered immutable
  stack and production-like scheduled/off-host RPO evidence. Feed the exact
  candidate identity forward into #1298 after those results exist.
- Do not change the canonical Compose/root-env/generated-OME contract, CI
  workflows, runtime services, release workflow, or default verification gate.

### Risks and boundaries

- A public manifest alone is not signature verification. Record the manifest
  SHA-256 and exact asset match while explicitly stating that Sigstore was not
  verified by this drill; reuse the clean-host/release verification evidence
  for that separate claim.
- Archive extraction is a pre-mutation trust boundary. Verify tag, commit,
  schema, unique asset entry, size, and SHA-256 before extraction, then refuse
  absolute or parent-traversing members before invoking `tar`.
- Review hardening must also bind the exercised `--bundle-root` bytes to the
  verified launcher archive subtree. The low-level published-mode driver
  accepts those paths independently; completeness alone cannot prevent a
  caller from pairing a valid archive with a modified extracted bundle.
  Compare a deterministic content inventory for every regular file and
  directory beneath `share/bitriver-live` before publishing the passing stage.
- The rehearsal intentionally destroys only a generated temporary source-host
  root. It must never accept the repository root, operator paths, or an
  existing target root, and it must scan retained evidence for test secrets.

### Test and rollout plan

- Add focused Python tests for valid public-package binding and schema,
  tag/commit/name/size/hash mismatch refusal. Exercise the report in both local
  and published modes and keep remaining-acceptance fields honest.
- Add shell regression coverage for exact script-root selection, identity
  overrides, pre-extraction verification, unsafe archive-member refusal,
  cleanup, and evidence retention. Run the existing local disaster drill.
- Once RC21 publishes, download its exact Linux amd64 launcher and release set,
  run the published disaster drill, scan retained evidence, and record the
  candidate/report hashes. Then run syntax/ShellCheck, focused tests, literal
  `./scripts/verify.sh`, protected PR CI, squash merge, and a bounded #1299
  handoff.

## Current scope - reusable single-host capacity qualification harness (#1303) (2026-08-21)

- Implement the target-host harness foundation requested by the refreshed
  roadmap: a versioned synthetic 1080p scenario, concurrent RTMP publishers,
  HLS virtual viewers, authenticated chat/API activity, protected metrics
  sampling, optional co-located Docker/host resource sampling, and a
  machine-readable `bitriver.capacity-report/v1` evidence artifact.
- Reuse the public-interface clients and synthetic media helpers from the
  production golden path. The harness must not read the application database,
  container filesystems, generated service configuration, or private operator
  state. Private accounts, sessions, and stream keys are per-run sentinels and
  may never appear in retained evidence.
- Separate scenario definition from execution. A checked-in RC qualification
  scenario defines warm-up, steady-state, spike, and bounded soak phases plus
  explicit host-protection stop conditions. A dry-run validates and hashes the
  exact scenario without contacting a stack; live execution requires the exact
  independently verified `release-set.json` bytes, validates their declared
  hash/tag/commit/five-image structure, and records every claimed collector
  boundary. It does not replace the release gate's Sigstore verification or
  infer that target containers match the release set.
- Sample API health, Prometheus text metrics, request latency/error rates,
  publisher state, viewer playlist/segment traffic, and load-client results at
  a bounded interval. When the runner is co-located with the Compose host,
  additionally collect host CPU/memory/disk and `docker stats`; otherwise mark
  host/container resource evidence unavailable rather than inferring it.
- Fail closed on invalid identities, unsafe workload sizes, unbounded phases,
  mismatched release-set bytes, missing protected metrics authorization,
  exceeded CPU/memory/disk/error thresholds, repeated health failures, early
  publisher exit, incomplete samples, undrained application work, or secret-
  bearing evidence. Always stop child publishers and load workers on success,
  failure, or interruption.
- Keep the wrapper portable across tracked-file mode differences: invoke the
  non-executable shared Python selector through Bash on host clients. Docker
  clients default loopback playback rewriting to `host.docker.internal` but
  must preserve an operator-supplied media-host override for remote targets.

### Roadmap ordering and evidence boundary

- #1297 remains the first production-acceptance issue, but the repository and
  issue history expose no physical XOA/Nginx Proxy Manager endpoint or access
  authority. Its remaining browser/media/firewall/reboot proof cannot be
  manufactured locally. #1303 is explicitly unblocked for reusable harness
  implementation against signed RC20 while the physical target is arranged.
- This slice establishes an executable qualification instrument and may run a
  bounded build-mode rehearsal. It does not publish a safe concurrency limit,
  correct host-sizing claims, close #1303, or stand in for target-host RC20
  measurements. Those require the exact supported physical host and its real
  topology, followed by reviewed raw results and conservative headroom.
- Do not change the canonical Compose/root-env/generated-OME contract, product
  runtime, monitoring deployment, CI workflows, or default verification gate.
  The harness is opt-in and targets an already-running canonical stack.

### Risks and boundaries

- Load generation can exhaust a developer or operator host. Enforce hard caps
  in the scenario parser, require stop conditions for every live scenario,
  ramp workloads by phase, and abort immediately when a configured protection
  threshold persists. The checked-in RC scenario stays intentionally small.
- Synthetic HLS fetchers measure HTTP/media delivery from the load client; they
  are not full browser decoders and do not prove WebRTC, playback UX, CDN, or
  browser compatibility. Those claims remain with #1307 and target-host media
  acceptance.
- API-level metrics do not expose host CPU, disk I/O, network, Postgres pool,
  Redis, or encoder internals by themselves. The report must distinguish
  direct host/Docker samples from application metrics and list uncollected
  requirements explicitly.
- Capacity fixtures persist normal test accounts/channels in the target stack.
  Require a dedicated qualification environment and state that cleanup is a
  disposable-environment responsibility; never target a live production data
  set by default.

### Test and rollout plan

- Add focused Python tests for scenario schema/identity validation, phase and
  stop-condition caps, Prometheus parsing, percent/resource parsing, latency
  summaries, stop-trigger behavior, deterministic scenario hashes, atomic
  sanitized reports, early-child cleanup, and secret refusal.
- Run the checked-in scenario in dry-run mode through both the host wrapper and
  Docker client image, then run a small disposable-stack rehearsal if local
  Docker resources permit. Scan every retained artifact for sentinels.
- Document the command, evidence schema, collector boundary, stop semantics,
  honest non-claims, and target-host follow-up in testing/operations guidance.
  Run Bash syntax/ShellCheck where available, Markdown links, focused tests,
  literal `./scripts/verify.sh`, protected PR CI, and squash merge.

## Current scope - viewer tooling dependency refresh (#1398) (2026-08-21)

- Reconcile the seven Dependabot development-tool updates with the repository's
  explicit viewer runtime baseline. The dependency graph moves
  `@types/node` from 26.1.2 to 26.2.0, so the invariant test must describe the
  reviewed package state instead of rejecting the intentional lockfile change.
- Keep the patch limited to development tooling, the baseline assertion, and
  concise workflow ledgers. Do not change viewer runtime dependencies,
  application behavior, deployment contracts, CI workflows, or release inputs.

### Risks and boundaries

- Type and lint package updates can expose compile, lint, test, accessibility,
  or production-build incompatibilities even when they do not ship in the
  runtime image. Validate the clean lockfile rather than accepting package
  metadata alone.
- Preserve the Node 24 and npm 11 engine contract. `@types/node` 26.2.0 remains
  a compile-time declaration update and must not be described as a Node 26
  runtime upgrade.

### Test and rollout plan

- Run the focused Go runtime-baseline test, clean `npm ci`, viewer lint/Jest,
  the production build, and the blocking production dependency audit.
- Run literal `./scripts/verify.sh --viewer`, staged diff/secret guards, and the
  protected PR matrix after updating the branch onto current `main`; squash
  merge only after every required check passes.

## Current scope - deterministic single-host service restart rehearsal (#1304) (2026-08-15)

- Add an opt-in, disposable rehearsal for the canonical single-host Compose
  stack that injects bounded stop/start failures into the API, Postgres, Redis,
  SRS/controller, OvenMediaEngine, transcoder, and viewer service paths. Record
  the expected public degradation signal for each dependency and the measured
  time until the stack returns to its documented ready state.
- Establish one authenticated account and channel before injection, then prove
  the session and durable channel state survive every recovery. Treat Redis
  queues and transient ingest work as explicitly ephemeral; do not overclaim
  in-flight delivery or media continuity from a control-plane restart check.
- Use `/readyz` for core dependency outages, authenticated `/api/status` for
  live ingest-component probes, and `/viewer` for the viewer boundary. Require
  bounded polling, no autonomous restart-count growth after recovery, and one
  machine-readable `bitriver.service-resilience/v1` report whose retained
  fields contain only release/source identity, component names, status classes,
  booleans, counts, and durations.
- Run from a clean tracked archive with a private copied environment, staged
  bind storage, and project-scoped database/cache volumes so the operator's
  working tree, local data, media, generated OME file, and root `.env` remain
  untouched. Fail closed when any
  canonical fixed-name container already exists; always tear down the isolated
  stack and evidence work directory unless retention is explicitly requested.
- Review hardening must pin `BITRIVER_CONFIG_ROOT` to the staged checkout root.
  A copied packaged/custom environment may otherwise retain an absolute
  operator config path and let the disposable SRS/OME renderers rewrite live
  generated configuration outside the rehearsal boundary.

### Risks and boundaries

- Do not change the canonical Compose/root-env/generated-OME contract,
  `scripts/verify.sh`, packages, CI, or runtime topology. This is a new opt-in
  qualification harness over the shipped contract, not a product behavior or
  deployment-workflow change.
- A local build proves restart and dependency-recovery mechanics only. Keep
  #1304 open for exact-candidate clean-host execution, physical daemon/host
  reboot, network partitions, disk/CPU/memory pressure, credentials/control-
  plane failures, active-stream continuity, and alert/delivery acceptance.
- Use randomly generated private test credentials only in the disposable work
  directory. Never print or retain them; scan the evidence against an exact
  sentinel set before declaring success.
- Bound every startup, degradation, and recovery wait. On interruption or
  failure, capture only sanitized diagnostics, return the failed stage, and
  remove containers, networks, volumes, copied environment, and credentials.

### Test and rollout plan

- Add focused helper tests for cookie persistence, readiness and ingest-status
  classification, durable-state checks, restart-count stabilization, report
  validation, timeout/failure reporting, secret rejection, and an absolute
  operator config-root override that resolves only to the staged tree.
- Run Bash syntax and pinned ShellCheck over the orchestrator, then execute the
  complete build-mode Compose rehearsal with real Postgres/Redis/media services
  and verify no canonical containers or test volumes remain afterward.
- Align resilience, production-release, testing, and draft release guidance
  with the report schema, command, proven boundaries, and remaining #1304
  acceptance; run Markdown links, diff/secret guards, and literal
  `./scripts/verify.sh` before a focused PR.

## Current scope - packaged-host disaster-recovery foundation (#1299) (2026-08-15)

- Extend the manifest-bound Postgres backup with one encrypted packaged-host
  recovery set for the default single-host profile. The set will contain the
  verified three-file Postgres backup, `/etc/bitriver-live`, and
  `/var/lib/bitriver-live` (API state plus transcoder/media data), while its
  public manifest retains only exact release identity, hashes, aggregate tree
  counts/fingerprints, encryption metadata, and timestamps.
- Use a passphrase file with OpenSSL AES-256-CBC plus PBKDF2 rather than placing
  secret material on argv, in environment variables, or in reports. Publish the
  encrypted archive, JSON manifest, and checksum atomically. Restore must verify
  the complete set and expected release before decrypting, refuse a non-fresh
  destination, validate every archive path, stream extraction without a
  plaintext archive on disk, and compare the recovered tree fingerprints.
- Ship the canonical Postgres and host recovery tools in every source-free
  launcher/package payload. Add an opt-in isolated rehearsal that creates
  representative database/config/media/object state, backs it up, removes the
  source deployment, restores into a fresh packaged-host-shaped root, restores
  Postgres into a fresh database, and emits one secret-scanned measured report.
- Reuse the production golden path after recovery rather than adding a parallel
  product smoke implementation. Bind the rehearsal to explicit release/commit
  identity and immutable image inputs; distinguish source-free bundle proof in
  this PR from the exact published-package run required after the next candidate
  is cut.

### Risks and boundaries

- Never read or modify the repository root `.env`, generated OME configuration,
  operator media, or unrelated deployment helpers. Test roots, encryption keys,
  Postgres databases, containers, volumes, reports, and decrypted state remain
  isolated and are removed by default.
- Do not change the canonical Compose/root-env/generated-OME contract, packages,
  CI workflows, or runtime topology. Adding recovery files to the existing
  source-free asset inventory changes package contents only; the installed
  runtime paths and service behavior remain unchanged.
- Require canonical packaged-host paths inside the encrypted archive and
  dereference compatibility symlinks during backup so a fresh installer can
  safely normalize them. Refuse traversal, absolute, device, or unexpected
  archive entries before extraction.
- Snapshot config, local data, the verified Postgres set, and object inventory
  into the private work directory before encryption; derive manifest
  fingerprints from that immutable snapshot so live writes cannot make a
  checksum-valid set whose restored tree disagrees with its manifest.
- Bind disaster evidence to the exact Postgres archive/manifest hashes and
  source identity carried by the host recovery report. Treat reported disaster
  RPO as the maximum of host-snapshot and database-backup age rather than
  allowing a recent host wrapper to hide stale system-of-record data.
- The default single-host proof covers local API/transcoder/media objects.
  External object-store durability remains an operator-managed replicated input;
  its non-secret inventory contract and consistency requirements must be carried
  in the recovery evidence without embedding credentials or object contents.
- This branch cannot prove its own future published package. Keep #1299 open
  until the merged recovery assets are published in an immutable candidate and
  the artifact-only clean-host recovery rehearsal plus production golden path
  pass against that exact release set.

### Test and rollout plan

- Add focused Python validation tests and shell integration cases for success,
  wrong release, checksum corruption, unsafe archive members, non-fresh target,
  wrong passphrase, atomic collision refusal, unrelated Postgres evidence,
  combined RPO, post-archive live mutation, root-importable unit execution, and
  secret-free reports.
- Verify the source-free asset inventory and packaged-host lifecycle expose the
  canonical recovery commands without a source checkout.
- Run the disposable Postgres/host-state rehearsal, production golden path when
  Docker resources permit, Bash/ShellCheck, docs/link/diff/secret checks, and
  literal `./scripts/verify.sh`.
- Publish through a focused high-risk PR and protected CI, post bounded evidence
  to #1299, then use the next immutable candidate for the final clean-host drill.

## Current scope - exact-image Compose upgrade and rollback rehearsal (#1298) (2026-08-15)

- Extend the merged database-layer foundation with a separate opt-in full-stack
  rehearsal for the same immutable `v1.2.3-rc.19` to `v1.2.3-rc.20` pair. Fetch
  the public `release-set.json` assets, require their recorded SHA-256 values,
  source commits, first-party image inventories, and exact third-party
  dependency inventories, then run only the recorded `tag@sha256` references.
  The pair retains the same eight dependency tags but upgrades the pinned
  Postgres 15 manifest digest; treat that as part of the stateful transition.
- Materialize clean source and candidate deployment trees from their recorded
  Git commits rather than the caller's working tree. Generate one rotated,
  private source environment from the published digest evidence, carry the
  same credentials/non-default settings forward, and change only release/image
  identity for the candidate. Use one isolated Compose project with persistent
  named database/Redis/transcoder volumes across source, candidate, and
  rollback trees.
- Start RC19 dependencies and API, record its observed aggregate-health state
  without treating it as source approval, and load the shared real-schema
  representative fixture into that deployment. Capture fixed-record and count
  invariants plus generated OME/SRS config hashes before changing services.
- Execute a documented stop/preflight/backup/upgrade sequence. At the
  post-migration/config but pre-application cut point, require the public health
  endpoint to remain unavailable so an interrupted deployment cannot look
  healthy. Start RC20, verify every first-party container uses the manifest
  digest, require exact source-fixture invariants, and run the existing
  production golden path against the upgraded stack.
- Roll back to the exact RC19 image/config set without removing persistent
  volumes. Require source image references, byte-identical generated config,
  preserved source fixtures and candidate-created row growth, successful
  readiness, and a reachable recorded aggregate-health response. Emit one
  secret-scanned JSON report and operator-readable Markdown summary.

### Risks and boundaries

- Never use or modify the operator's root `.env`, generated OME file, local
  media directory, or unrelated deployment helpers. All checkouts, secrets,
  release manifests, reports, volumes, networks, and containers are isolated
  and removed by default; retained evidence contains only hashes, counts,
  booleans, release identities, image digests, and durations.
- Do not edit the canonical Compose/root-env/generated-OME contract, packages,
  or CI workflows. The rehearsal consumes the exact tagged contract in clean
  temporary trees and remains opt-in because it pulls public release assets and
  images and runs the expensive media golden path.
- RC19 is a rejected source release. Its rollback phase may prove image/config/
  state mechanics and readiness, and the report records the observed aggregate
  status without requiring it to reproduce the earlier startup failure. It
  cannot be described as an approved production rollback target. #1298 remains
  open until a healthy approved previous-version rollback and clean-host
  package/reboot evidence exist.
- Use the merged manifest-bound Postgres backup before candidate mutation. This
  slice exercises the in-place rollback class; destructive-schema restore
  classification remains covered by the database rehearsal and must be rerun
  for any future migration-changing pair.

### Test and rollout plan

- Extract reusable fixture/invariant SQL from the data-plane rehearsal and keep
  its exact positive, restore, and interruption behavior green.
- Add the exact-release Compose orchestrator with release-set refusal cases,
  source/candidate/rollback image and config assertions, the interrupted cut
  point, upgraded golden-path evidence, sanitized reports, and guaranteed
  teardown.
- Run Bash/ShellCheck, helper/unit checks, both stateful rehearsals, docs/link/
  secret/diff checks, and literal `./scripts/verify.sh`. Publish through a
  focused high-risk PR and protected CI, then post bounded evidence to #1298.

## Current scope - stateful RC19 to RC20 upgrade/rollback data-plane rehearsal foundation (#1298) (2026-08-15)

- Use the immediate immutable published pair `v1.2.3-rc.19` at commit
  `1e14e3cf7d5f1d949b396d4f7897660575ea468e` and `v1.2.3-rc.20` at
  commit `9a8516a60c584c96a46b630b55c46df33f46fbdc`. Bind evidence to
  their public release-set SHA-256 identities
  `374a4084d1880abab1fa980d528a47bb5e324ed85541248438015fb13f2cc204`
  and `dd8eabcea7cf920a6f520e3e472cf44d3e1c7b0b7ad74945904f67ea74a47873`.
  RC19 is an upgrade-source fixture, not an approved release claim.
- The two tags have no migration-runner or migration-file diff. Classify this
  exact hop as in-place compatible at the database/migration layer only: apply
  the RC19 migration set, load representative state in the real schema, take a
  manifest-bound backup, execute the RC20 migration plan/apply path, and prove
  exact ledger plus row/value invariants before and after an in-place rollback
  decision.
- Exercise the restore-required decision separately by changing the upgraded
  state, restoring the verified RC19 backup into a fresh isolated database, and
  proving the original invariants. Demonstrate that an ambiguous `applying`
  ledger state blocks the candidate migration path rather than allowing a
  falsely successful upgrade.
- Emit one secret-safe JSON report with exact release identities, schema
  fingerprints, representative invariant results, rollback classification,
  blocked-interruption evidence, and observed backup/upgrade/rollback durations.

### Risks and boundaries

- Never log or retain DSNs, passwords, auth/MFA secrets, session tokens, stream
  keys, or row contents. The report may contain release/schema hashes, aggregate
  counts, boolean/value-hash matches, timings, and rollback decisions only.
- Use actual canonical migrations and table shapes rather than the simplified
  backup-script fixture. Keep all databases/backup artifacts disposable and
  isolated from the operator's root `.env`, generated OME, and runtime media.
- Do not change Compose, root env, generated OME, packages, or CI workflows.
  This foundation does not close #1298: exact-image Compose upgrade, config and
  image rollback, meaningful interrupted deployment cut points, ingest/playback,
  chat/admin/VOD APIs, and the full production golden path remain required.
- RC19 and RC20 have an identical schema, so no destructive-migration rollback
  claim is possible from this pair. Future schema-changing releases must rerun
  the rehearsal and may be classified restore-required or unsupported.

### Test and rollout plan

- Add a Docker-backed Postgres 15 rehearsal with actual migrations plus
  representative admin/creator/moderator/viewer, auth/MFA/session, profile,
  channel/follow/schedule, moderation/legal/chat, upload/recording/object, and
  non-default setting fixtures.
- Require the manifest-bound backup/restore scripts, migration planner/apply
  path, deterministic invariant snapshots, ambiguous-ledger refusal, JSON
  report schema/content, secret scan, and clean database/container teardown.
- Run shell/static checks, the focused rehearsal, docs/link/diff/secret checks,
  and literal `./scripts/verify.sh`. Publish as a focused PR through protected
  CI, then keep #1298 open for the exact-image full-stack phase.

## Current scope - manifest-bound Postgres backup/restore rehearsal foundation (#1299) (2026-08-15)

- Harden the existing `backup-postgres.sh` / `restore-postgres.sh` path instead
  of creating a parallel recovery implementation. Current backups are compressed
  and checksummed, but restore treats the checksum as optional, captures no
  release/schema/database/tool identity, checks only table presence, emits no
  machine-readable result, and has no automated corruption or compatibility
  regression.
- Produce an atomic gzip archive, mandatory checksum, and adjacent JSON manifest
  for each successful backup. Bind the manifest to operator-supplied release and
  commit identity, the database/server and pg tool versions, a deterministic
  applied-migration fingerprint, and exact row counts for every public base
  table. Refuse backup while the migration ledger is absent or non-applied.
- Before creating a rehearsal database, restore must require and verify the
  checksum/manifest, archive identity, manifest schema, and optional expected
  release/schema fingerprint. Restore only into a fresh isolated database,
  then compare the restored migration set and exact public-table row counts with
  the signed-by-checksum backup manifest.
- Emit a secret-safe JSON report with backup age (observed RPO), restore duration
  (observed RTO), compatibility/invariant results, and cleanup/retention state.
  This evidence is a release input, not proof of off-host storage or a complete
  lost-host recovery by itself.

### Risks and boundaries

- Never include passwords, DSNs, object credentials, host addresses, row data,
  or operator configuration contents in manifests/reports. Database/table names,
  release identity, schema checksums, counts, tool versions, timings, and result
  booleans are the only evidence fields in scope.
- Missing checksum or manifest becomes a hard restore refusal. Document that
  legacy dumps must be revalidated/repackaged outside production rather than
  silently restored; checksum or compatibility failure must occur before any
  rehearsal database is created or dropped.
- Keep database identifiers strictly validated before interpolation. Preserve
  cleanup traps, allow explicit retained rehearsal databases, and never restore
  over the source database.
- This slice covers Postgres only. Operator configuration/secrets/certificates,
  `/var/lib/bitriver-live`, local transcoder/media state, and object-storage
  versioning/replication remain required durable inputs; Redis stays explicitly
  ephemeral in the default contract. Do not claim #1299 complete until an
  artifact-only lost-host rebuild plus media/object invariants and the production
  golden path pass.
- Do not change Compose, root env, generated OME, package layout, CI workflows,
  or deployment topology in this slice.
- Treat a timestamped backup set as an owned transaction. A concurrent or
  same-second producer must refuse the collision without deleting an existing
  archive, manifest, or checksum; cleanup may remove final paths only after the
  current process has exclusively claimed that set.
- Preserve the advertised Helm scheduled-backup capability by sourcing its
  producer from the canonical `scripts/backup-postgres.sh`. Keep the generated
  chart copy under the existing Helm asset-sync check so the scheduler cannot
  emit the legacy two-file format that hardened restore intentionally refuses.
  This does not change the canonical Compose/root-env/generated-OME contract.

### Test and rollout plan

- Add a Docker-backed real-Postgres integration test with non-empty role/auth,
  channel, schedule, moderation/legal, chat-filter, upload/recording, and
  migration fixtures. Require backup, isolated restore, exact schema/row-count
  invariants, a measured JSON report, and cleanup.
- Add negative cases proving archive corruption, missing checksum/manifest,
  mismatched expected release, and mismatched schema fingerprint fail before
  the named rehearsal database exists.
- Add a deterministic same-second collision regression proving the first
  complete three-file set remains byte-identical after the second producer is
  refused. Render/check the Helm producer from the synchronized canonical
  script and require the chart to publish the same three-file restore contract.
- Run shell/static checks, focused integration, Markdown/link, diff/secret
  hygiene, and literal `./scripts/verify.sh`. Publish through a focused PR and
  protected CI; keep #1299 open for the remaining lost-host/media/object/golden
  path acceptance after this foundation lands.

## Current scope - prime aggregate ingest health after RC19 rejection (#1297, #1304) (2026-08-09)

- Treat immutable `v1.2.3-rc.19` as rejected clean-host evidence. Release run
  `31351022453` published 46 public assets from exact green main commit
  `1e14e3cf7d5f1d949b396d4f7897660575ea468e`; all 45 checksum entries match,
  the signed release-set SHA-256 is
  `374a4084d1880abab1fa980d528a47bb5e324ed85541248438015fb13f2cc204`,
  and its five first-party images are digest-bound. Do not patch RC19 or move
  its tag.
- Hosted no-checkout run `31351694175` verified the signed release set and
  package, installed on Ubuntu 24.04.4 x86_64, passed production preflight,
  activated the pull-only systemd stack, passed all seven CLI smoke checks,
  and reached authenticated OME control. It then failed the aggregate OME
  assertion because `/healthz` returned the startup snapshot
  `ingest: disabled` instead of probing the configured controller.
- Exact-image Docker Desktop reproduction confirms every ingest URL and
  credential is present. Both storage constructors seed a non-empty disabled
  snapshot with a current timestamp; `/healthz` intentionally trusts any
  recorded snapshot, so its documented live fallback is unreachable on first
  boot and the configured SRS/OME/transcoder controller is hidden indefinitely
  until another route explicitly refreshes it.
- Start repository health caches empty. The first `/healthz` request must then
  perform one bounded live probe and record the result; later requests retain
  the existing cache behavior. A genuinely disabled deployment still reports
  `ingest: disabled` through the no-op controller after that first probe.

### Risks and boundaries

- Do not make every `/healthz` request fan out to ingest services. Only the
  missing initial snapshot should trigger the existing live fallback; once
  recorded, the cache and `/api/status` refresh behavior remain unchanged.
- Coalesce callers that arrive while the initial probe is in flight so startup
  liveness and rollout smoke cannot duplicate downstream SRS/OME/transcoder
  requests or race to overwrite the first snapshot. Calls that begin after the
  initial snapshot is recorded must retain the existing explicit refresh path.
- Keep JSON and Postgres repository behavior aligned so development/test and
  the production installer cannot diverge. Do not change Compose, `.env`, OME
  configuration, authentication, public ports, or the release contract.
- Preserve the private root `.env`, tracked generated OME placeholder bytes,
  and the six operator-owned untracked paths. Exact-image diagnostic resources
  must be removed after evidence is captured.

### Test and rollout plan

- Extend the shared repository health scenario to require an empty/zero initial
  snapshot before the first controller probe, then require the first and later
  probes to populate/update the cache for both JSON and real Postgres backends.
  Add a blocked-controller concurrency regression proving overlapping initial
  calls share one probe and one recorded result for both repositories.
- Retain API handler coverage proving `/healthz` uses a valid cache and falls
  back to a live probe only when no snapshot exists. Update the operations
  contract to state that the first request primes the cache.
- Run focused storage/API tests, real Postgres-tagged tests, documentation/link,
  diff/secret hygiene, and literal `./scripts/verify.sh`. Rebuild the API in an
  isolated Docker Desktop project and require `/healthz` to report exactly one
  `ovenmediaengine: ok` entry before cleanup.
- Publish through a focused PR and protected CI, merge only when the aggregate
  gate is green, rerun exact-main CI, and cut only the next unused immutable
  candidate (`v1.2.3-rc.20`). Independently verify its public release set and
  rerun the complete no-checkout qualification through upgrade, restart
  recovery, retained uninstall, sanitized evidence, and cleanup.
- Even a hosted pass remains bounded evidence. Keep #1297/#1304 and stable epic
  #1293 open for the real XOA/NPM/firewall/host-reboot/media path and the other
  state, capacity, resilience, SLO, security, and browser gates.

### Qualification result and roadmap handoff (2026-08-15)

- PR #1391 merged without bypass as exact main commit
  `9a8516a60c584c96a46b630b55c46df33f46fbdc`; protected PR run
  `31911599915` and exact-main run `31911889892` passed every required gate.
- Immutable `v1.2.3-rc.20` release run `31912204601` passed and published 46
  public assets. Independent verification found 45 unique checksum entries,
  exact candidate commit/tag identity, complete payload coverage, and signed
  release-set SHA-256
  `dd8eabcea7cf920a6f520e3e472cf44d3e1c7b0b7ad74945904f67ea74a47873`.
- No-checkout Ubuntu 24.04 qualification run `31912782711` passed package
  installation, immutable preflight, pull-only activation, smoke and
  authenticated OME control, same-tag upgrade staging, OME/Docker/systemd
  restart recovery, retained-data uninstall, sanitized evidence, and cleanup.
- The ledger closeout reran literal `./scripts/verify.sh` successfully with the
  current public contract values in a disposable env copy. Real Postgres,
  Compose, rebuilt quickstart, endpoint health, and teardown passed; the private
  root `.env` was restored at SHA-256
  `9D57F7161B241315158B0654CA51DA997A8BBF9408A1D6E944AE39648D91AAC2`,
  and generated OME bytes remained unchanged.
- This completes the hosted RC20 forward-fix slice. It does not close #1297,
  #1304, or #1293: physical XOA/Nginx Proxy Manager/firewall/host-reboot/media
  evidence and the remaining production gates are still required. The next
  primary recoverability slice is #1299; #1298 explicitly depends on its proven
  backup/restore path.

## Current scope - separate post-start smoke from pre-start port readiness after RC18 rejection (#1297, #1304) (2026-08-09)

- Treat immutable `v1.2.3-rc.18` as rejected clean-host evidence. Release run
  `31348429963` published 46 public assets from exact green main commit
  `4e496e389d2a606976a55a89cd08ef79762eed2f`; all 45 checksum entries match,
  the signed release-set SHA-256 is
  `2de0cb71af2af8d41d7a535d28478003987d6b180568a84bc5dc3eae680ef014`,
  and its five first-party images are digest-bound. Do not patch RC18 or move
  its tag.
- Hosted no-checkout run `31348986535` verified the public signature and
  package checksum, installed on Ubuntu 24.04.4 x86_64, staged immutable
  inputs, passed production preflight, and activated the pull-only systemd
  stack. It then failed `smoke-and-ome-control` because `bitriver smoke` calls
  the complete pre-start `doctor` with default paths after the stack is
  running; doctor correctly sees BitRiver's own 23 TCP/UDP listeners as port
  conflicts, so the post-start command can never advance to its Compose and
  endpoint checks on a normal installation.
- Preserve `bitriver doctor` and quickstart host-port refusal as pre-start
  safety gates. Narrow only the smoke prerequisite stage to the Docker CLI,
  daemon, and Compose availability/version checks it claims to perform; smoke
  must then use its caller-selected Compose/env files for running-stack state
  and endpoint checks. Do not add a public bypass flag or weaken preflight.

### Risks and boundaries

- Skipping all doctor logic would hide a stopped Docker daemon or unsupported
  Compose version. Reuse the existing binary/version check implementations and
  minimums while excluding pre-start-only port availability, host sizing, and
  bind-mount writability from the post-start phase.
- Keep `verify` ordering intact: doctor remains first, before Compose config and
  smoke. A standalone post-start smoke must not require ports to be free.
- Update the smoke command documentation to state the phase boundary. This is
  a CLI behavior correction, not a deployment-contract, Compose, env, or OME
  configuration change.
- Preserve the private root `.env`, tracked generated OME placeholder bytes,
  and the six operator-owned untracked paths.

### Test and rollout plan

- Add focused Go coverage proving the smoke prerequisite checks reject missing
  Docker/Compose but do not invoke host-port availability checks, and retain
  existing running-stack/endpoint failure coverage.
- Run focused `./cmd/bitriver` tests, documentation/link checks, diff/secret
  hygiene, and literal `./scripts/verify.sh`. Exercise the running-stack smoke
  against Docker Desktop so occupied BitRiver ports are accepted only after
  Compose state and health endpoints pass.
- Publish through a focused PR and protected CI, merge only when the aggregate
  gate is green, rerun exact-main CI, and cut only the next unused immutable
  candidate (`v1.2.3-rc.19`). Independently verify its public release set and
  rerun the complete no-checkout clean-host qualification through smoke,
  authenticated OME control, upgrade, restart recovery, retained uninstall,
  sanitized evidence, and cleanup.
- A hosted pass remains bounded evidence. Keep #1297/#1304 and stable epic
  #1293 open for the real XOA/NPM/firewall/host-reboot/media path and the other
  state, capacity, resilience, SLO, security, and browser gates.

## Current scope - align installed generated config after RC17 rejection (#1297, #1304) (2026-08-09)

- Treat immutable `v1.2.3-rc.17` as rejected clean-host evidence. Hosted run
  `31344257847` passed the no-checkout boundary, signed provenance, public
  package install, disabled-service staging, immutable production inputs,
  doctor, and preflight, then failed systemd activation because `ome-config`
  exited 1. Do not patch RC17, move its tag, or weaken activation.
- Align the managed Ubuntu configuration layout with the source-shaped path
  already used by the read-only Compose helpers: persist OME output at
  `/etc/bitriver-live/deploy/ome/Server.generated.xml` and SRS output at
  `/etc/bitriver-live/deploy/srs/conf/srs.generated.conf`, while the installed
  `/opt/bitriver-live/deploy/...` paths remain symlinks to those operator-owned
  files.
- Migrate the former flat `/etc/bitriver-live/Server.generated.xml` and
  `/etc/bitriver-live/srs.generated.conf` files without replacing operator
  content. Move a sole legacy file into the canonical tree, retain bounded
  compatibility symlinks, accept an idempotent already-migrated layout, and
  reject divergent dual copies instead of guessing which one to discard.
- Keep the private environment at `/etc/bitriver-live/bitriver.env`, data under
  `/var/lib/bitriver-live`, and the existing `BITRIVER_CONFIG_ROOT` mount. This
  is an installed generated-config path correction, not a new deployment
  topology or another OME/SRS render path.

### Root cause, risks, and boundaries

- PR #1381 correctly moved helper writes off the read-only `/workspace` mount
  and into `/etc/bitriver-live/deploy/...`. Source checkouts passed because
  those directories already exist. The package installer still stored the two
  generated files flat under `/etc/bitriver-live`; SRS happened to create its
  missing parent directories, while the OME Go renderer uses `os.WriteFile`
  and failed on the absent parent. Creating the OME directory alone would be
  unsafe because the OME consumer would still read the old flat file.
- Protected PR run `31346223822` exposed a second contract edge after the path
  fix: SRS cannot read an operator-owned mode-`0600` generated config while it
  runs with all Linux capabilities dropped. The tracked OME placeholder masked
  the equivalent fresh-install risk because an existing file keeps its prior
  mode when `os.WriteFile` truncates it. Preserve `bitriver.env` as owner-only,
  but make the two generated runtime configs mode `0640`, owned by the selected
  Docker operator UID/GID, and add only that GID as a supplementary group for
  the read-only OME/SRS consumers. Do not add capabilities or make either
  runtime mount writable. Both renderers must enforce the final mode on every
  render, including existing files.
- Corrected run `31346863473` proved the runtime group-read design in the base
  Compose file but exposed a Linux smoke-fixture mismatch: its override assigns
  the runner UID/GID to the config renderers without persisting or exporting
  `BITRIVER_HOST_GID`, so the base SRS/OME supplementary group resolves to `0`.
  Keep the production/package contract unchanged and add the fixture's own
  `host_gid` to only its SRS/OME services. Do not export host identity globally,
  because this smoke intentionally retains the transcoder image UID on its
  isolated named volume.
- Third run `31347182741` passed the complete product matrix after that fixture
  alignment, including Ubuntu test-all and cross-platform Go/entrypoint checks.
  Its merge gate failed only because PR #1386's prose risk section did not use
  the repository's machine-checked scorecard headings/checkboxes. Preserve the
  high-risk classification and evidence, update the PR body to the exact
  template shape, and trigger one fresh synchronize run before merging.
- Upgrade migration must be fail-safe under interruption and must not erase a
  modified operator config. Use same-filesystem moves where possible, create
  parents before migration, apply the established `0600` file and operator
  ownership boundary, and make reruns deterministic.
- Keep `/etc/bitriver-live` mode-restricted; do not loosen credentials, grant
  capabilities, make the helper workspace writable, or retain raw renderer
  logs in qualification evidence.
- Update `docs/contract.md` and operator/install guidance in the same change.
  Preserve the private root `.env` and the six operator-owned untracked paths.

### Test and rollout plan

- Extend the Linux installer lifecycle for a fresh structured layout, migration
  from legacy flat files, compatibility symlinks, byte preservation, exact
  ownership/modes, idempotent reinstall, and divergent-copy refusal.
- Add static release/Compose contracts that the helper outputs, installed
  symlinks, and documented paths all name the same source-shaped files. Run an
  exact package-style Docker fixture using arbitrary non-root UID/GID and prove
  SRS render, OME render, token verification, and consumer-visible file bytes.
- Exercise actual SRS/OME consumer startup under dropped capabilities so the
  renderer mode, operator group, and runtime supplementary-group contract is
  proven rather than inferred from a renderer-only fixture.
- Run focused installer/Go/shell/docs checks, literal `./scripts/verify.sh`,
  protected PR CI, and exact-main CI. Publish only the next unused immutable
  candidate, `v1.2.3-rc.18`, from that green main commit; independently verify
  its public signed release set and rerun the no-checkout qualification.
- A hosted pass remains bounded evidence. Keep #1297/#1304 open until the real
  XOA/NPM/firewall/reboot/media requirements assigned to those issues pass.

## Current scope - define and reconcile the live-room chat target (#1272) (2026-08-09)

- Add one product-source-of-truth specification under `docs/` that turns the
  ivlog-inspired direction into an adapted BitRiver Live target state. Cover
  desktop and mobile layout, dense message anatomy, the room roster, roles and
  badges, system and moderation rows, accessibility, busy-room performance,
  and an MVP-versus-later boundary without copying ivlog.tv assets, branding,
  CSS, or exact visual treatment.
- Audit each visible feature against the current `/api/chat/ws` protocol, Go
  gateway, viewer chat panel, and responsive channel page. Mark each item as
  shipped, protocol-only/partially wired, or requiring a backend/viewer change;
  do not treat closed implementation issues as proof when the production call
  path is absent.
- Link the completed foundation issues #1273, #1274, and #1275, then create
  focused follow-up issues for concrete gaps discovered by the audit. Keep one
  canonical chat stack: existing business and transport behavior stays in the
  Go service/chat/API boundaries, and the Next.js viewer consumes that protocol.
- Link the specification from the contributor-facing architecture/UI docs so a
  future implementation PR can find the target state without chat history.

### Findings, risks, and boundaries

- The viewer already ships the video-first desktop dock and mobile-below-video
  layout, dense bounded transcript, follow-versus-read scroll behavior,
  loading/retry/empty/auth states, a scoped accessible live log, reports,
  moderator row actions, and supported slash-command parsing.
- The Go gateway already emits de-duplicated `presence_snapshot`,
  `presence_join`, and `presence_leave` events and carries normalized role and
  badge metadata. The viewer does not currently consume these presence events;
  its visible roster is inferred from recent messages, and badge payloads are
  parsed but not rendered as real badge elements.
- `BroadcastSystemEvent` and the `system` envelope exist and the viewer can
  render live system rows, but no production service currently calls the
  broadcaster for stream lifecycle or pinned-room notices. `/me`, persistent
  message removal/room clearing, slow mode, and title/announcement management
  remain unsupported protocol/product decisions and must not be implied as MVP.
- This slice is documentation and issue decomposition only. It does not change
  the deployment contract, chat wire format, authorization rules, persistence,
  or user-supplied content rendering. The private root `.env` and the six
  operator-owned untracked paths remain untouched.

### Test and rollout plan

- Validate every shipped/partial/missing claim against named source files and
  existing tests, then run the repository Markdown-link checker and
  `git diff --check`.
- Run the focused Go chat tests and viewer chat-panel tests because the spec is
  a product contract derived from those behaviors; run literal
  `./scripts/verify.sh` before publication as required by repository policy.
- Publish through a focused protected PR. Merge only after required checks are
  green, verify exact-main CI, and close #1272 only when the checked-in spec and
  linked follow-up issue set satisfy every epic acceptance criterion.

### Completion evidence

- Added `docs/live-room-chat.md` with the adapted-not-copied boundary, all
  eleven requested product areas, a source-backed feature/protocol matrix,
  MVP/later scope, and the single-stack architecture contract. Filed remaining
  MVP #1382, #1383, and #1384 after auditing closed foundation #1273-#1275.
- Focused chat/viewer/docs checks and literal repository verification passed.
  PR #1385 passed protected run `31344956999`, squash-merged as exact main
  `8e9f46f146e7e065cb097a53103a5317fd77230e`, and exact-main run
  `31345003256` passed. #1272 closed as completed and the repository returned
  to zero open pull requests.

## Current scope - artifact-only Ubuntu host qualification (#1297, #1304) (2026-08-03)

- Add a manual, least-privilege Ubuntu 24.04 amd64 qualification workflow that
  performs no repository checkout. It must download one public prerelease's
  signed release set, exact `.deb`, checksums, and five image signature bundles;
  verify their exact tag/workflow identity; and use only those published bytes
  for installation and runtime acceptance.
- Install the package on the clean hosted runner, stage the disabled systemd
  service for a non-root Docker operator, derive immutable image pins from the
  signed manifest, validate the production environment, and activate the
  canonical pull-only Compose stack. Exercise `doctor`, `smoke`, authenticated
  OME manager access, same-tag upgrade, OME restart, Docker daemon restart,
  full systemd restart, status, ordinary uninstall, package removal, and
  config/data retention.
- Emit a sanitized `bitriver.clean-host-qualification/v1` JSON report even on
  failure. Bind it to candidate tag, source commit, release-set SHA-256,
  package checksum, exact image digests, OS/architecture, tool versions,
  workflow run, and per-stage outcomes without retaining credentials, raw env,
  generated media credentials, OME XML, or broad logs.
- Refresh first-install documentation from superseded RC12 wording to the
  public signed RC13 release set. Keep Ubuntu 24.04 amd64 as the sole production
  installation target and keep Debian/RPM/arm64, real XOA reboot, Nginx Proxy
  Manager browser/media routing, firewall/NAT, and target-host ingest/playback
  explicitly provisional.

### Risks and boundaries

- A GitHub-hosted Ubuntu VM can prove a clean package/systemd/Docker lifecycle,
  but it cannot prove the user's XCP-ng/XOA reboot path, router/firewall rules,
  or Nginx Proxy Manager TLS/WebSocket/media topology. #1297 and #1304 remain
  open until those external checks are attached to the same RC13 manifest hash.
- Never trust the candidate tag alone. Require the operator-supplied release-
  set hash, verify the root with checksum-verified Cosign against the exact
  release workflow/tag identity, verify all five exact image bundles, and
  populate Compose digests from that signed root before Docker pulls.
- The qualification job runs with generated ephemeral credentials. It must not
  print or upload `/etc/bitriver-live/bitriver.env`, generated OME/SRS config,
  container logs, stream keys, or credentials. Diagnostics are restricted to
  safe versions, systemd state, Compose status, and explicit stage outcomes.
- Preserve the canonical Compose/env/generated OME contract, the private root
  `.env`, and the six operator-owned untracked paths. This slice adds a consumer
  of the release contract; it does not create a second deployment topology.

### Test and rollout plan

- Add focused workflow contract tests for manual-only triggering, no checkout,
  least privilege, exact release-set/image signature verification, release-
  only package install, non-root systemd lifecycle, bounded OME/Docker/systemd
  recovery, retention-safe uninstall, always-produced sanitized evidence, and
  explicit external-gate boundaries.
- Parse workflow YAML, run the focused Go contract test, installer-language and
  Markdown-link checks, committed-secret guard, `git diff --check`, and literal
  `./scripts/verify.sh` with the private environment restored byte-for-byte.
- Merge only through the protected aggregate gate and require exact-main CI.
  Then dispatch the workflow for `v1.2.3-rc.13` and release-set SHA-256
  `795fffee84662aec91624eb4352b9c1a9ef5c34b17838939adaf567418797fa0`.
- If the live run passes, download and independently inspect its report, commit
  the durable redacted evidence in a follow-up, and attach it to #1297/#1304.
  Do not close either issue or enable stable promotion until the XOA/NPM/reboot
  and remaining production gates genuinely pass.

### Live rollout correction

- The first post-merge dispatch was rejected by GitHub before a run existed:
  `runner.temp` is not an available expression context in job-level `env`.
  Move qualification path construction into the first Bash step using the
  runner-provided `$RUNNER_TEMP`, append only those non-secret paths to
  `$GITHUB_ENV`, and keep every later step on the same isolated directories.
- Add a contract assertion that the workflow never uses `${{ runner.* }}` in
  job-level configuration and requires `$RUNNER_TEMP` initialization before
  any path is consumed. Rerun focused/YAML/policy checks and the literal
  verifier, then land the correction through PR and exact-main gates before
  redispatching RC13. The rejected dispatch is parser evidence only and must
  not be reported as a clean-host execution attempt.
- Run `30822618916` proved the parser correction and no-checkout boundary, then
  stopped before package installation because exact Cosign 3.1.2 supports
  `--bundle` for `verify-blob` but not container-image `verify`. Keep bundled
  root verification unchanged; checksum each downloaded image bundle against
  its signed image entry, signed artifact entry, and `CHECKSUMS.txt`, then use
  Cosign's supported anonymous registry-backed `verify` command for each exact
  immutable image digest and release-workflow identity. The five public RC13
  bundle hashes match all three signed/checksum sources, and the exact Cosign
  binary verifies all five remote digests anonymously with that command.
- Extend the workflow contract so image bundle byte verification is mandatory
  and container verification rejects the unsupported `--bundle` flag. Run the
  focused/YAML/policy and literal verifier gates, land through a new protected
  PR and exact-main run, and redispatch the same immutable RC13 inputs. Treat
  `30822618916` as a provenance-stage compatibility failure only; its sanitized
  artifact may be inspected, but it is not clean-host lifecycle evidence.
- Run `30824712866` then proved signed provenance, public package installation,
  and disabled systemd staging. Env validation and `bitriver-host doctor`
  passed, but the digest-render assertion omitted disabled Compose profiles and
  therefore could not see the signed `alpine:3`/`alpine:3.19` references used
  only by the optional `postgres-host` and `srs-api` services. Render the audit
  with `docker compose --profile '*' ... config --images` so all canonical
  services are checked without enabling those profiles during activation.
- Add a contract assertion for all-profile digest rendering, repeat focused and
  literal gates, protected PR/main validation, and redispatch exact RC13. Treat
  `30824712866` as package/preflight evidence only, not activation or lifecycle
  evidence; its failure artifact and successful cleanup remain diagnostic.
- Run `30827018880` passed the no-checkout/provenance boundary, public `.deb`
  installation, disabled-service staging, immutable environment validation,
  doctor, and the complete all-profile digest audit. Activation then exposed a
  real immutable RC13 package defect: the rendered systemd unit retained quotes
  around `WorkingDirectory=/opt/bitriver-live`, which systemd rejects as a
  non-absolute path. Do not patch the installed candidate in qualification or
  weaken the activation gate; RC13 is rejected for clean-host qualification.
- Fix the canonical packaged Compose unit and add rendered-unit regression
  assertions at the installer boundary. Run focused installer/unit checks plus
  the literal repository verifier, then land the correction through a protected
  PR and exact-main CI. Publish the next unused immutable candidate, RC14, only
  from that exact green main commit and rerun the artifact-only qualification
  against RC14's signed release-set hash. RC13 and run `30827018880` remain
  bounded failure evidence; stable promotion and #1297/#1304 remain open.
- The post-rebase Linux lifecycle exposed that the canonical `.service`
  template is stored with CRLF because the repository attributes cover shell
  scripts but not systemd units. Enforce LF for this exact packaged unit and
  normalize its tracked bytes; keep the strict rendered-directive regression
  so Windows checkouts cannot mask release-byte drift. Repeat the Linux
  lifecycle, focused policy checks, and fresh PR CI before merge.
- PR #1378 passed protected run `31333807382`, squash-merged as exact main
  `9f97c1533613fd9a1cb40353c2df1d159c51a2aa`, and exact-main run
  `31334103956` passed. Immutable `v1.2.3-rc.14` was published from that commit;
  release run `31334396479` passed all 46 public assets and the independently
  verified signed-root SHA-256 is
  `ae27e14f4e3883e216c57c145e73d8571838e8b80098afd7145cc6ed9f5923f3`.
- Clean-host run `31334880405` proved RC14 provenance, public `.deb` install,
  disabled systemd staging, production environment validation, doctor, and
  the complete immutable-image audit. The corrected systemd unit parsed and
  invoked the package launcher, but activation stopped because both
  `srs-config` and `ome-config` exited 1. RC14 remains immutable and rejected
  for clean-host qualification; do not patch its installed bytes or move its
  tag.
- The package intentionally keeps `.env`, OME output, and SRS output under
  `/etc/bitriver-live` and exposes them in `/opt/bitriver-live` as absolute
  symlinks. The renderer containers mount `/opt/bitriver-live` at `/workspace`,
  so `/workspace/.env` and both generated paths resolve to container-local
  `/etc/bitriver-live`, which is currently absent. This explains the paired
  renderer failure before SRS/OME consumers can start.
- Add one Compose contract value, `BITRIVER_CONFIG_ROOT`, defaulting to the
  source checkout root. Mount it at `/etc/bitriver-live` only in `srs-config`,
  `ome-config`, and `ome-health-token-check`; the two renderers receive a
  writable bind while the verifier receives it read-only. The packaged
  installer must idempotently set the value to its absolute operator config
  directory, while source checkouts retain the relative default and existing
  generated-file consumer mounts.
- Extend installer and Compose contract tests to require the managed package
  value, exact renderer/verifier mounts, read-only verification, source-default
  rendering, and no credential output. Re-run the Linux package lifecycle,
  focused Go/shell/contract checks, isolated Docker Compose rendering, and the
  literal verifier while preserving the private root `.env` and six unrelated
  operator paths.
- Automated PR review identified that the Unix launcher initially derived the
  config root before `ensure_assets` created a custom env file's missing parent
  directory. Move derivation immediately after env seeding and lock that order
  in the wrapper regression so first-run custom paths remain supported.
- PR #1379 passed refreshed protected run `31336273494`, squash-merged as exact
  main `91525a32f43d202561a9cc12d9d5452f48813f16`, and exact-main run
  `31336615980` passed. Immutable `v1.2.3-rc.15` published from that commit in
  passed release run `31336980513`. Independent public verification accepted
  all 46 assets, the release-set signature, and all five exact image signatures;
  the signed-root SHA-256 is
  `69acc96dae732fe9e6eea6f0a8953f57bd3796a5ddc373fec81872a81c9abb2e`
  and the amd64 `.deb` SHA-256 is
  `d54347660312ded8266387efa65a6abe543ac9ab3cd52532b6fdcf64942e4202`.
- Clean-host run `31337618217` advanced RC15 through signed provenance,
  package installation, disabled service staging, immutable production input
  configuration, environment validation, and doctor. Systemd activation still
  stopped because `srs-config` and `ome-config` both exited 1; sanitized
  evidence upload and cleanup passed. RC15 remains immutable and rejected.
- An exact downloaded-package reproduction proves the installed symlinks,
  renderer commands, and `/etc/bitriver-live` mount work when the correct source
  is selected. The remaining durable defect is that `bitriver env init` seeds
  the source-checkout value `BITRIVER_CONFIG_ROOT=..` into the installed
  `/etc/bitriver-live/bitriver.env`; only the systemd process and launcher
  attempted to override it. Persist the absolute installer config directory in
  that managed env file on install/upgrade, without printing or replacing other
  operator values. Append the key for older package env files that predate it,
  replace the single source default, and reject ambiguous duplicates.
- Extend the installer lifecycle regression to require exactly one persisted
  absolute config-root entry after first install and idempotent reinstall, while
  retaining unrelated operator values. Repeat the exact installed-package
  renderer proof, focused tests, literal verifier, protected PR/exact-main CI,
  next unused immutable candidate, and no-checkout qualification.
- Land the correction through a focused protected PR and exact-main CI. Publish
  only the next unused immutable candidate from that green commit, independently
  verify its signed public release set, and rerun the no-checkout qualification.
  A hosted pass still does not close #1297/#1304 or prove XOA reboot,
  Nginx Proxy Manager, firewall/NAT, browser playback, or target-host media.
- PR #1380 passed refreshed protected run `31338969678`, squash-merged as exact
  main `0a6be1600b08bcf2a4caeca6c580698f7e2f8ce4`, and passed exact-main run
  `31339266505`. Immutable `v1.2.3-rc.16` published from that commit in passed
  release run `31339591099`; all 46 public assets, the signed release root, and
  all five exact image signatures independently verify. The release-set
  SHA-256 is
  `c4d34bd82264995723a679b88e497c1a02aa192a99ac8bf3458de771b7b34b79`
  and the amd64 `.deb` SHA-256 is
  `a8e02419b1fbc51e2477030e32cca69145aa25d3be259dca513c3f77cdd5363a`.
- Clean-host run `31340245262` proved RC16 signed provenance, public package
  installation, disabled systemd staging, persisted config-root resolution,
  production environment validation, doctor, and the all-profile immutable
  image audit. Activation still stopped when both config renderers exited 1;
  sanitized evidence upload and cleanup passed. RC16 remains immutable and
  rejected for clean-host qualification.
- The remaining paired failure is a Unix ownership boundary. The installer
  correctly keeps `/etc/bitriver-live` and durable bind data mode-restricted and
  owned by the non-root Docker operator, but Compose forces the renderer jobs,
  API, and transcoder to unrelated image UIDs. With all capabilities dropped,
  the renderer jobs cannot traverse the private config directory; the API and
  transcoder would likewise be unable to write their durable package paths.
- Add optional `BITRIVER_HOST_UID`/`BITRIVER_HOST_GID` Compose inputs. The
  packaged installer must atomically persist the resolved operator IDs, while
  Unix launch wrappers derive them for source/archive starts when absent.
  Apply them only to services that write operator-owned bind mounts:
  `srs-config`, `ome-config`, `ome-health-token-check`, `bitriver-live`, and
  `transcoder`. Preserve each image's existing UID fallback when the values are
  absent outside the managed installer.
- Reduce the renderer namespace while correcting ownership: mount the installed
  workspace read-only, move the sanitized SRS helper copy to tmpfs, and keep
  only `/etc/bitriver-live` writable for the two render jobs. Do not grant new
  Linux capabilities, loosen mode `0600` secrets, chown operator config to a
  container-only account, or upload renderer logs containing credentials.
- Extend Compose, installer, wrapper, and release-contract regressions for exact
  UID/GID persistence, idempotent upgrade, duplicate refusal, image-specific
  fallbacks, read-only workspaces, tmpfs execution, and arbitrary non-root
  bind writes. Run Linux lifecycle/package fixtures, permission-faithful exact
  image rendering, Docker Compose validation, focused Go/shell tests, literal
  `./scripts/verify.sh`, protected PR/exact-main CI, and then publish only the
  next unused immutable candidate (RC17) for another no-checkout qualification.
- Protected PR run `31341715983` proved the ownership, docs, ShellCheck, native
  arm64 viewer, and blocking image-scan gates, but the Ubuntu smoke exposed a
  path-alias regression: `ome-config` still writes the generated file through
  the now-read-only `/workspace` mount. The writable config-root mount already
  aliases the same source-checkout or installed target, so make both renderers
  write directly under `/etc/bitriver-live/deploy`, make the verifier read from
  that same canonical mount, and keep templates/assets read-only. Add a static
  Compose assertion plus a real source-checkout render that proves the host
  generated path changes without granting workspace write access. Re-run the
  focused renderer tests, literal verifier, and fresh protected CI; do not
  weaken mounts or bypass the failed gate.
- Automated review also identified that `scripts/quickstart.sh` derives process
  UID/GID values before Compose reads the selected env file. Because process
  values win over `--env-file`, an explicit operator owner in `.env` can be
  silently replaced by the invoking account. Detect both supported env-file
  flag forms without sourcing or printing the file; if either managed identity
  key has a non-empty env-file value, export neither fallback and let Compose
  plus `env validate` retain or reject the pair. Derive both current IDs only
  when neither the process nor selected file declares an identity. Exercise
  the wrapper with stubbed Go for default derivation and both explicit flag
  forms, then rerun shell/static/full verification.

## Current scope - publish and inspect the first signed release-set candidate (#1271, #1301) (2026-08-03)

- Use `v1.2.3-rc.13`, the next unused candidate tag after the public RC12, and
  point it at the exact protected-main commit that passed CI. Do not move or
  recreate any prior tag.
- Let the candidate-only release workflow build once, sign all five exact image
  digests, exercise the anonymous pull-only product gate, generate/sign the
  deterministic release-set root, and publish the complete prerelease assets.
- Inspect the public GitHub release, checksum coverage, release-set signature,
  exact tag/commit/workflow binding, five image signature bundles, evidence,
  SBOMs, and anonymous GHCR visibility before calling the candidate valid.
- Close #1271 only if the public candidate proves its complete provenance
  contract. Keep #1301 and every external stable gate open: publishing a signed
  candidate is not byte-identical stable promotion or Ubuntu/XOA qualification.

### Risks and boundaries

- Tag publication is externally visible and immutable in normal operation. Tag
  only the exact green main commit and never repair a bad candidate by moving
  the tag; fix forward with another prerelease.
- Registry propagation, keyless signing, scanners, multi-architecture builds,
  and the pull-only media stack are network-dependent. Treat a failed run as
  evidence to diagnose, not permission to weaken or bypass a gate.
- Preserve the private root `.env` and the six operator-owned untracked paths.
  A candidate does not authorize stable aliases, `latest`, or production
  exposure through Nginx Proxy Manager.

### Test and rollout plan

- Merge the completed promotion rollout ledger through the protected aggregate
  gate and require exact-main CI to remain green.
- Create an annotated `v1.2.3-rc.13` tag at that exact main commit and push only
  the tag. Monitor the release workflow to a terminal result.
- Verify public release assets and digest/checksum/signature relationships with
  the repository verifier plus direct GitHub/GHCR inspection. Confirm the
  release is a prerelease and that `v1.2.3` and `latest` remain untouched.
- Record the workflow/release evidence in `TASKS.md` and the relevant issues.
  If publication fails, retain the failed tag/run for audit and fix forward.

### Outcome

- Ledger PR #1367 squash-merged as `d416968e`; exact-main CI run
  `30795396504` passed before the annotated `v1.2.3-rc.13` tag was created at
  that exact commit and pushed without moving an earlier tag.
- Release run `30795492882` passed every environment, Postgres, Go, package,
  multi-platform build, exact-image signing, anonymous pull-only product,
  payload scan, release-set signing, and publication job. The public release is
  a prerelease with 46 assets and no stable or `latest` alias.
- The complete public download passed `release_set.py verify-candidate`; every
  downloaded SHA-256 matched GitHub's server digest. `release-set.json` has
  SHA-256 `795fffee84662aec91624eb4352b9c1a9ef5c34b17838939adaf567418797fa0`
  and binds RC13, commit `d416968e`, run `30795492882`, 42 payload artifacts,
  five evidence files, eight passed workflow gates, and eight pending external
  gates.
- Checksum-verified Cosign v3.1.2 independently verified the signed release-set
  root and all five public image signatures against the exact
  `release.yml@refs/tags/v1.2.3-rc.13` identity and GitHub OIDC issuer. Empty-
  credential GHCR inspection resolved all five RC13 tags to their manifest
  digests and confirmed every `v1.2.3`/`latest` alias remained absent.
- The public golden-path evidence passed real RTMP ingest, live/offline state,
  OME LL-HLS playlist advancement and decoded 1080p H.264 media, transcoder HLS,
  chat/moderation, VOD upload/playback, and final aggregate readiness. #1271 is
  closed; #1301 and #1297/#1298/#1299/#1303-#1307 remain open for actual stable
  promotion and target Ubuntu/XOA/NPM qualification.

## Current scope - immutable candidate release sets and stable promotion (#1301, #1271, #1302) (2026-08-03)

- Make prerelease candidate tags the only build inputs. A stable tag must never
  invoke the build workflow or move `latest`; stable publication is a separate
  manual promotion from one already-published candidate tag and digest set.
- Add a deterministic `bitriver.release-set/v1` generator/verifier. The signed
  root manifest records the candidate tag and commit, workflow run, every
  public artifact checksum/size, five first-party image digests and SBOM/
  signature references, pinned third-party digests, toolchain policy, gate
  results, evidence hashes, and explicit skipped/remaining external gates.
- Sign all five image manifest digests with keyless Cosign, verify those exact
  digests during the anonymous pull-only product gate, then sign the release-
  set manifest. Publish the scanner-approved manifest, bundle, checksums, and
  fixed-schema evidence as candidate release assets.
- Add a `workflow_dispatch` stable-promotion workflow on protected `main`.
  Its read-only `Stable promotion gate` downloads the candidate assets, checks
  GitHub asset digests, checksum coverage, root/image signatures, revocation
  state, the candidate commit, and a tracked promotion record whose required
  gate issues/evidence all target the same release-set hash.
- Only after validation may an environment-approved job retag exact image
  digests, generate stable and previous-stable rollback manifests, copy the
  unchanged candidate assets into a draft stable release, verify all aliases,
  optionally update convenience-only `latest`, and publish the draft. Reruns
  must be idempotent and must reject any existing tag, asset, or alias that
  points at different bytes.
- Add an environment-approved candidate-revocation operation that publishes a
  signed, append-only revocation marker. Promotion refuses any candidate with
  that marker; no prior stable asset or alias is overwritten by revocation.
- After the workflow merges and exact-main CI passes, configure the public
  repository's `stable-promotion` environment with a required reviewer,
  self-review allowed for the single-owner project, and protected-branch-only
  deployment. Do not invent completion evidence for the still-open Ubuntu/XOA,
  recoverability, resilience, capacity, security, or browser gates.

### Risks and boundaries

- Cross-repository image aliases and GitHub release publication are not one
  atomic transaction. Validate every immutable input before the first write,
  publish through a draft release, retain no-op/retry semantics, and keep
  production truth at candidate tag plus digest rather than `latest`.
- Candidate assets intentionally retain their candidate filenames/package
  metadata when promoted; renaming or rebuilding them would violate identical-
  byte promotion. Stable metadata points back to the candidate release set.
- Keyless signature verification must bind the exact repository workflow
  identity, tag ref, and GitHub OIDC issuer. Do not accept wildcard identities,
  disable claim checking, or treat an unsigned checksum file as provenance.
- Promotion evidence may arrive after the candidate build. It must be a tracked
  main-branch record with durable URL plus SHA-256 references and the exact
  signed release-set hash; expiring Actions logs alone are insufficient.
- Do not change the deployment contract, runtime code, private root `.env`, or
  the six user-owned untracked deployment/runtime paths in this slice.

### Test and rollout plan

- Unit-test deterministic manifest output, checksum coverage, exact image/
  evidence schemas, signature references, promotion-record gate completeness,
  revoked candidates, stable/candidate tag matching, rollback metadata, path
  traversal, duplicate assets, tampering, and idempotence classifications.
- Extend workflow contracts for candidate-only tag triggers, no build-time
  `latest`, pinned Cosign installation, exact-digest signing/verification,
  signed release-set publication, least-privilege promotion permissions,
  environment approval, no build steps, validation-before-mutation, draft
  publication, rollback metadata, and refusal to overwrite mismatched state.
- Run Python fixtures, YAML parsing, action pinning/policy checks, focused Go
  workflow tests, ShellCheck where applicable, documentation checks, secret
  scanning, `git diff --check`, and literal `./scripts/verify.sh`.
- Require full PR CI, exact merged-main CI, live environment API readback, and
  a deliberately unverified manual promotion run that fails in `Stable
  promotion gate` before any environment or registry mutation. A new immutable
  RC is the later end-to-end manifest proof; stable promotion remains blocked
  until its external gate record is genuinely complete.
- Literal verification with a digest-complete synthetic environment exposed a
  source-build smoke seam: SRS is the only third-party image that also has a
  local build target, and Compose cannot use its pull-only `@sha256` suffix as
  the resulting build tag. In build-mode smoke only, clear
  `BITRIVER_SRS_IMAGE_DIGEST` alongside the five first-party build digests and
  reapply those overrides after sourcing the env file before `compose up`;
  preserve every pin in pull/production mode and add a shell contract fixture
  so release digest enforcement is never weakened.

### Outcome

- PR #1366 squash-merged as `38cfeb13` after PR CI run `30793307725` and its
  protected `Merge gate` passed. Exact-main CI run `30793780301` also passed,
  including the unified Ubuntu gate, cross-platform Go/quickstart jobs, image
  scans, and aggregate gate.
- GitHub environment `stable-promotion` was created and read back with required
  reviewer `ProhibitedTV`, self-review allowed for the single-owner project,
  and protected-branch-only deployment.
- Deliberate negative run `30794248391` rejected the absent tracked promotion
  record in the unprivileged `Stable promotion gate`; both mutation jobs were
  skipped, and no `v1.2.3` tag or release was created. Issue evidence was added
  to #1271, #1301, and already-closed #1302 without claiming external gates.

## Current scope - enforce one aggregate merge gate (#1302, #1270) (2026-08-03)

- Preserve the consolidated, path-filtered reusable workflow graph. Add one
  always-run `Merge gate` that evaluates the result of every existing child
  job against the changed-file outputs and fails on failed, cancelled, or
  unexpectedly skipped required work. Do not duplicate child test commands.
- Reuse `scripts/check-pr-release-scorecard.sh`. Keep low-risk/docs-only
  scorecards advisory, but make the scorecard blocking when changed paths
  automatically indicate workflow, deployment, migration, auth/security, or
  release/operator risk. Publish the scorecard and aggregate table as a
  human-readable Actions artifact and job summary.
- Live GitHub audit found no rulesets and `main` is not protected. After the
  implementation PR and exact merged-main CI pass, require the stable `Merge
  gate` context with strict up-to-date status, pull requests, conversation
  resolution, admin enforcement, and no force pushes or deletion.
- Demonstrate the aggregate failure locally and in the implementation PR before
  correcting its scorecard. After protection is active, use a disposable
  intentionally failing canary PR to prove the protected branch blocks a bad
  aggregate result, then close it and delete its branch.
- This slice completes #1270's gate clarity/artifact boundary and the merge half
  of #1302. Stable-promotion enforcement remains explicitly blocked on #1301's
  immutable release-set manifest and is not reimplemented here.

### Risks and boundaries

- GitHub reports conditional jobs as `skipped` both when they are correctly
  irrelevant and when an upstream required job fails. Expected-job decisions
  must therefore come from the changed-file outputs, while every unexpected
  failure/cancellation blocks regardless of path selection.
- The aggregate job must run with `if: always()` and depend on every child job;
  otherwise GitHub can skip the aggregate precisely when it is most needed.
- PR body content is untrusted input. Read it from `GITHUB_EVENT_PATH`, never
  interpolate it into shell source, and keep workflow permissions read-only.
- Do not change the deployment contract, release artifacts, runtime behavior,
  private root `.env`, or the six user-owned untracked deployment/runtime paths.

### Test and rollout plan

- Add fixture coverage for docs-only success, selected-job success, required
  skip, child failure/cancellation, unexpected extra failure, and scorecard
  failure; add workflow contracts for complete `needs`, `if: always()`, stable
  job naming, full-history checkout, summary, and pinned artifact upload.
- Extend scorecard tests for advisory low-risk and blocking risky-path modes.
  Run shell syntax/ShellCheck, YAML parsing, pinned Go script tests, CI contract,
  committed-secret guard, `git diff --check`, and literal `./scripts/verify.sh`.
- Require an intentional red aggregate run followed by corrected full PR CI,
  exact merged-main CI, live branch-protection API verification, and a blocked
  disposable canary PR before calling the merge enforcement active.

### Outcome

- PR #1363 merged as `200bf414`; exact-main run `30787937537` passed every
  selected child and the stable `Merge gate`.
- Live `main` protection now requires the strict, app-bound GitHub Actions
  `Merge gate`, pull requests, current-head checks, resolved conversations, and
  admin enforcement; force pushes and branch deletion are disabled.
- Ready canary PR #1364 failed the required gate in run `30788386196`, GitHub
  reported it `BLOCKED`, and it was closed unmerged with both branch refs
  removed. Issue #1270 is closed; #1302 stays open only for #1301's immutable
  stable-promotion work.

## Current scope - remove tracked generated Go caches (2026-08-02)

- Remove the four root `.gocache-*` directories that were accidentally added
  by commit `b4617f75`. They contain 6,775 generated Go build-cache objects and
  416,199,766 bytes in the current tree; no source, workflow, documentation, or
  release path references them.
- Preserve repository history, tags, releases, and every non-cache path. This
  is a HEAD cleanup, not a history rewrite: future checkouts stop materializing
  the 416 MB cache payload, while old commits remain reproducible.
- Keep the existing `/.gocache-*/` and `.gocache/` ignore rules. Add a focused
  repository-hygiene regression to reject any tracked root Go cache even when
  it is force-added, and run that guard through the canonical verification
  path rather than creating another overlapping workflow.
- PR #1361 exposed a release-blocking large-diff seam in the existing CI
  orchestrator: `dorny/paths-filter` used GitHub's pull-request files API,
  received only the first 3,000 of 6,780 changed paths, and therefore skipped
  every substantive job even though three `scripts/**` files changed. Keep the
  pinned action, but use its documented empty-token mode so the complete
  checkout and unbounded `git diff` drive pull-request routing.
- Close the preceding documentation task with its exact merged-main evidence:
  PR #1354 merged as `f3952624`, `main` CI run `30779028786` passed, and the
  remote returned to 58 branches after the PR head was deleted.

### Risks and boundaries

- A deletion-only cache diff is mechanically large. Build the removal set from
  `git ls-files` under the four exact audited roots, verify that all deleted
  entries match those roots, and never use a broad recursive cleanup against
  the repository.
- Do not touch the private root `.env`, generated OME configuration, or the
  user's six untracked deployment/runtime paths.
- Do not claim that deleting tracked files removes their objects from existing
  Git history or immediately shrinks every established clone; repository
  history rewriting is deliberately out of scope.
- Do not weaken path selectivity or force every expensive job for every PR.
  The correction must preserve the existing filters while changing only their
  complete changed-file source.

### Test plan

- Prove the new hygiene guard accepts the cleaned tree and rejects a temporary
  repository with a force-added root `.gocache-*` artifact.
- Require the tracked deletion inventory to contain exactly 6,775 files under
  the four audited roots and zero other paths; run `git diff --check` and the
  committed-secret guard.
- Add a CI workflow regression that requires the changed-file detector's
  supported Git fallback plus full-history checkout, and prove PR #1361 now
  selects at least the Ubuntu, ShellCheck, and quickstart jobs for the changed
  canonical verification scripts.
- Run literal `./scripts/verify.sh`, PR CI, and exact merged-main CI before
  considering the cleanup complete.

### Completion outcome

- PR #1361 squash-merged as `5e736f2b`; exact merged-main CI run
  `30785253246` passed the complete selected matrix.
- The PR head was deleted and stale local tracking refs were pruned. The remote
  now has 64 branches rather than the earlier 58 because Dependabot created six
  separate viewer dependency heads during the documentation/cleanup window.
  Those bot-authored, non-audited heads are preserved as concurrent work; they
  are not residue from the cleanup branch or justification for blind deletion.

## Current scope - first-time installer documentation refresh (2026-07-31)

- Treat published prerelease `v1.2.3-rc.12` as the current consumer artifact
  baseline. Its 33-asset release is checksum-complete; downloaded Linux amd64
  launcher and `.deb` payloads contain the exact RC12 tag for all five
  first-party images; retained scans and the eight-stage pull-only product gate
  passed at commit `3a9572f0`.
- Rewrite the README for two clear audiences: a Windows user evaluating the
  full stack with Docker Desktop from a checkout, and an Ubuntu 24.04 amd64
  operator installing the published package/archive for an XOA VM and Nginx
  Proxy Manager deployment. Lead with outcomes, prerequisites, exact commands,
  first-stream flow, and links to deeper operator runbooks.
- Keep the two committed screenshots because they are provenance-known captures
  of the shipped viewer; remove stale release-availability language, malformed
  punctuation, unsupported GitHub Pages guidance, and repeated source-only or
  planned-`rc.1` copy. Prefer concise workflow diagrams/text over promotional
  imagery that is not runtime evidence.
- Align quickstart, Ubuntu install, viewer deployment, release index/notes,
  changelog, production status/release, operations, and support navigation with
  the shipped RC12 commands and assets. Strengthen documentation consistency
  checks so the most damaging pre-release-only wording cannot return.

### Documentation evidence boundary

- Say exactly what RC12 proves: Docker Desktop source verification on this
  Windows host, package install/remove acceptance on Ubuntu 24.04/Debian/Rocky,
  anonymous five-image pulls, OME-backed ingest/live playback, offline
  transition, chat/moderation, and VOD playback in the tagged product gate.
- Do not claim a clean XOA VM installation, Nginx Proxy Manager browser path,
  host reboot recovery, or repeated OME restart/media recovery has been
  exercised. Present the existing runbooks and acceptance commands for those
  remaining operator checks.
- Run focused wording/link/Markdown/YAML checks, the documentation consistency
  gate, `git diff --check`, and literal `./scripts/verify.sh`; then require PR
  CI and exact merged-main CI before updating the public repository front door.

## Previous scope - stamp exact image tags into release artifacts (2026-07-31)

- Preserve immutable published prerelease `v1.2.3-rc.11` at commit
  `96e99fd6`. Its workflow, checksums, images, pull-only product gate, and
  publication evidence passed, but a direct download/extraction audit found a
  release-blocking installer defect: the Linux `.deb` and launcher archive
  both seed all five first-party image tags as stable `v1.2.3`, while RC11
  publishes only `v1.2.3-rc.11` images.
- Keep the canonical root/development `deploy/.env.example` defaults at stable
  `v1.2.3`. Add an explicit, validated release-tag input to the canonical
  staging helper and rewrite only the staged copy's five
  `BITRIVER_*_IMAGE_TAG` values. Require every release bundle, launcher,
  Linux-package, and Windows-installer staging call to pass the immutable Git
  tag.
- Add focused regressions that prove prerelease-tagged staged payloads use the
  exact tag for all five first-party images, the source env remains unchanged,
  and release workflow packaging cannot omit the tag argument.
- Do not present RC11 as a working first-time Ubuntu installation path. After
  focused checks and literal `./scripts/verify.sh`, require complete PR CI and
  exact merged-main CI before creating immutable `v1.2.3-rc.12`.

### `rc.12` artifact-acceptance plan

- Accept RC12 only after its complete release workflow and pull-only tagged
  product gate pass, publication evidence is retained, and the GitHub
  prerelease has a checksum-complete asset inventory.
- Download the published RC12 Linux `.deb` and launcher archive, extract both
  independently, and confirm that each installed/staged `.env.example` names
  `v1.2.3-rc.12` for API, viewer, SRS controller, transcoder, and OME config.
- Keep clean Ubuntu/XOA installation, Nginx Proxy Manager browser access,
  reboot recovery, and repeated OME restart/media recovery as separate,
  unproved promotion evidence until exercised on the target VM.
- Resume the consumer README/docs refresh only after the published package
  audit passes, so the documented first-install path is based on an artifact
  users can actually run.

## Previous scope - ancestry-safe remote branch cleanup (2026-07-31)

- Preserve published prerelease `v1.2.3-rc.11`, every tag, `main`, and every
  branch whose tip is not an ancestor of current `origin/main`.
- The immediate post-release fetch/prune and GitHub API inventory agreed on
  1,002 real remote branches: `main`, 942 ancestry-merged non-default tips, and
  59 non-ancestor tips. GitHub reported no protected branches.
- Delete only those 942 ancestry-merged refs. Every 50-ref atomic batch must
  follow a fresh fetch and confirm exact object IDs, ancestry, and the current
  open-PR exclusions; stop on drift or a failed remote update.
- During the final reconciliation, Dependabot PRs #1346 and #1344 merged and
  GitHub deleted their two previously preserved heads while advancing `main`
  from `96e99fd6` to `d3740828`. Those heads were not members of the deletion
  set. The resulting authoritative inventory is `main` plus 57 non-ancestor
  branches, with zero merged non-default branches remaining.

### Branch-cleanup verification outcome

- All 942 candidate names and object IDs were classified before mutation and
  revalidated across 19 atomic batches (18 of 50 refs and one of 42).
- The final local remote-ref inventory and GitHub API both report 58 branches;
  ancestry classification reports zero merged non-default tips and 57 retained
  non-ancestor tips.
- The two-branch difference from the expected 60 was classified as concurrent,
  successful PR merges with normal head deletion, not accidental cleanup.
- Keep the six user-owned untracked deployment paths and the private root
  `.env` untouched.

## Previous scope - publish file-backed release notes (2026-07-31)

- Preserve immutable `v1.2.3-rc.10` at verified main commit `48e4b878` and
  failed release run `30665278361`. The failed-job rerun passed the pull-only
  tagged product gate, scanned the complete publication payload in 31 seconds,
  retained publication evidence, and generated release notes. No GitHub
  prerelease exists. Do not rerun, move the tag, or publish it manually.
- Repair only the final publication input boundary. Because this is the
  repository's first GitHub Release, generated notes contain the full prior
  history; passing that body inline to the Node action exceeded the runner's
  process argument limit before the action started.
- Write the generated body as UTF-8 under `RUNNER_TEMP`, expose only its short
  path as a step output, and use the release action's supported `body_path`
  input. Do not truncate the changelog, weaken the atomic finalizer, or bypass
  its complete dependency/evidence gates.
- Add a workflow contract regression that requires file-backed notes and
  rejects the oversized inline-body form.

### `rc.11` test and publication plan

- Run the focused release-workflow regression, complete `go test ./scripts`,
  workflow/action YAML parsing, both CI policy checks, `git diff --check`, and
  literal `./scripts/verify.sh` while preserving the private root `.env`
  byte-for-byte.
- Require complete PR CI and exact merged-main CI before creating annotated,
  immutable `v1.2.3-rc.11`.
- Accept RC11 only after every release job passes, the pull-only OME-backed
  product gate succeeds, publication evidence is retained, the GitHub
  prerelease exists, and its asset/checksum inventory is complete.
- Clean Ubuntu/XOA installation, Nginx Proxy Manager browser access, reboot,
  and repeated OME restart/media recovery remain separate promotion evidence.

## Previous scope - bound release payload scanning (2026-07-31)

- Preserve immutable `v1.2.3-rc.9` and failed release workflow `30655699977`.
  All application, package, image/SBOM, and pull-only tagged product gates
  passed; the final scanner spent 59 minutes finding 224 false-positive
  assignments plus RPM extraction errors, then failed before publication. No
  GitHub prerelease exists. Do not rerun, move the tag, or publish manually.
- Treat the finalizer duration as a release-engineering defect independent of
  RC9's eventual terminal result. The scanner has processed a roughly 250 MB
  mixed artifact set since 18:45 UTC without completing; a local 8.3 MB subset
  containing the viewer bundle exceeded five minutes.
- Preserve every covered secret class, archive-depth limit, path-only finding,
  and no-secret-output property. Use ripgrep to prefilter credential-shaped
  matches and classify only bounded matches in one awk process per rule; retain
  a tested grep/awk fallback when ripgrep is absent. Distinguish real literal
  credential keys from package hashes, framework cache tokens, parser tokens,
  and ordinary code references without allowing literal `api_token` values.
- Exclude Buildx `.dockerbuild` records from the final release artifact
  download. They are workflow diagnostics, not publication payloads or SBOMs,
  and GitHub CLI cannot reliably extract at least one RC9 record as a ZIP.
- Give the payload-scan step an explicit timeout so a future pathological
  artifact blocks publication promptly instead of consuming the default job
  maximum. Do not weaken scan failures or publish on timeout.
- Extract RPM payloads with absolute filenames disabled so deep inspection
  remains inside the scanner scratch directory and valid packages do not fail
  as unreadable archives.

### Payload-scanner test plan

- Retain all focused positive/negative secret-scanner tests, including nested
  archives, sentinel values, forbidden filenames, assignments, credential
  URLs, XML credentials, the no-ripgrep fallback, and non-disclosure of matched
  values.
- Add workflow contracts for excluding `.dockerbuild` records and bounding the
  scan step. Add a high-line-count benign fixture with a practical elapsed-time
  assertion so the external-process regression is reproducible without a large
  binary artifact corpus.
- Re-run the complete scanner/release-workflow Go suites, shell syntax,
  workflow/action YAML parsing, CI policy checks, `git diff --check`, and the
  repository verification gate before publication. Re-scan the real RC9 viewer
  artifact subset and an actual RC9 RPM when available.
- After PR and exact-main CI pass, publish the next immutable candidate
  `v1.2.3-rc.10`; accept it only if the final scanner, retained evidence,
  complete release asset set, and GitHub prerelease creation all pass.

## Previous scope - repair the `rc.8` image publisher and publish `rc.9` (2026-07-31)

- Preserve immutable `v1.2.3-rc.8` at verified main commit `5295c1e4`.
  Release run `30653362368` proved production configuration, migrated
  Postgres, repository verification, every CLI/release/launcher build, the
  hosted MSI, Linux packages and package acceptance, Homebrew, the viewer
  bundle, native viewer images, and its assembled multi-architecture manifest.
  It did not create a GitHub prerelease.
- Repair the shared tag-only image publication boundary. The srs-controller,
  ome-config, and transcoder multi-architecture builds invoked their
  Dockerfiles without build arguments, so all three used the offline-oriented
  defaults `GOPROXY=direct` and `GOSUMDB=off`. Both `gopkg.in/check.v1` and
  `gopkg.in/yaml.v3` then returned HTTP 502 during the arm64 dependency fetch;
  the pull-only product gate and GitHub Release correctly skipped.
- Pass `https://proxy.golang.org,direct` and `sum.golang.org` only to the
  release image Buildx step, matching the already-proven quickstart and image
  scan build boundary. Keep host Go verification offline and leave runtime
  network, image names, Dockerfiles, and the deployment contract unchanged.
- Add a release-workflow contract regression so a future refactor cannot
  silently return tag-only builders to direct-only dependency resolution.

### `rc.9` test and publication plan

- Run the focused release workflow regression, complete Go `./scripts` suite,
  workflow/action YAML parsing, CI policy checks, and `git diff --check`.
- Run literal `./scripts/verify.sh --viewer` while preserving the private root
  `.env` byte-for-byte; require full PR CI and exact merged-main CI before
  creating annotated immutable `v1.2.3-rc.9`.
- Accept `rc.9` only after every release job passes, including all image/SBOM
  publishers, the pull-only tagged product gate, complete assets/checksums, and
  GitHub prerelease creation. Never move or reuse the failed `rc.8` tag.
- Clean Ubuntu/XOA installation, Nginx Proxy Manager browser access, reboot,
  and repeated OME restart/media recovery remain separate promotion evidence.

## Previous scope - repair the `rc.7` viewer-auth race and publish `rc.8` (2026-07-31)

- Preserve immutable `v1.2.3-rc.7` at verified main commit `2dfa77d0`.
  Release run `30648508975` proved the hosted MSI, both native viewer image
  architectures, the assembled viewer multi-architecture manifest, all other
  first-party images, Linux packages and package acceptance, Homebrew, and the
  pull-only tagged product gate. It did not create a GitHub prerelease.
- Repair the only failed producer at the product boundary. The viewer bundle
  passed install, audit, lint, Jest, and production build, then Playwright
  clicked the navbar sign-in action before the initial `/api/viewer/me`
  response had populated its configured `/login` URL. The UI opened the local
  auth flow (`/?auth=signin&next=%2F`) instead of redirecting to
  `/login?redirect=%2F`.
- Keep authentication actions unavailable while initial auth discovery is in
  progress, and make the configured-login browser regression wait for the
  mocked auth response before clicking. This prevents the observed user-facing
  misroute and proves the expected external-login contract deterministically.
- Harden the Docker dependency-fetch boundary exposed by PR CI. Both the
  initial run and failed-job rerun reached direct `gopkg.in` fetches and failed
  on HTTP 502 while native arm64 viewer proof passed. Keep host Go tests
  offline, but give quickstart/verification Compose builds and the reusable
  image-scan build an explicit `https://proxy.golang.org,direct` plus checksum
  database configuration. Do not change runtime networking or image tags.

### `rc.8` test and publication plan

- Add/adjust focused Navbar and Playwright regressions for initial auth loading
  and configured-login navigation. Add workflow/script contracts that require
  the build-only Go proxy in quickstart and image scan; keep the release
  workflow and offline host-test policy unchanged.
- Run viewer lint, Jest, Playwright, and production build, the relevant Go
  workflow contracts, workflow/action YAML parsing, `git diff --check`, and
  literal `./scripts/verify.sh --viewer` while preserving the operator `.env`
  hash.
- Require complete PR CI and exact merged-main CI before annotated immutable
  `v1.2.3-rc.8`. Accept the candidate only after every release job passes and
  GitHub publishes the prerelease with complete assets/checksums.
- Clean Ubuntu/XOA installation, Nginx Proxy Manager browser access, reboot,
  and repeated OME restart/media recovery remain separate promotion evidence.

## Previous scope - repair the `rc.6` hosted release failures and publish `rc.7` (2026-07-31)

- Preserve immutable `v1.2.3-rc.6` at verified main commit `d94ac432`.
  Release run `30643868431` proved the restored viewer baseline, Go/Postgres,
  every CLI/release/launcher matrix entry, Linux packages and all three
  package-acceptance hosts, Homebrew, and four first-party multi-architecture
  images. It did not create a GitHub prerelease.
- Repair the hosted MSI at the actual ICE validation boundary. Precomposed WiX
  definition arguments worked and `candle.exe` compiled both sources, but
  `light.exe` rejected both shortcut key-path rows with `ICE03: Invalid
  registry path`. The WiX XML incorrectly uses doubled backslashes in registry
  keys (and shortcut target paths), even though XML does not escape a
  backslash. Use canonical single-backslash Windows paths and retain ICE
  validation.
- Remove emulated Node installation from viewer image publication. The amd64
  image completed, while the arm64 Buildx leg crashed inside `npm ci` with
  `qemu: uncaught target signal 4 (Illegal instruction)` and never unwound.
  Build amd64 on `ubuntu-latest` and arm64 on GitHub's native
  `ubuntu-24.04-arm` runner, then assemble the public multi-architecture
  manifest and SBOM in a bounded finalizer job. Keep the other four image
  publishers unchanged apart from an explicit timeout.

### `rc.7` test and publication plan

- Add regressions requiring canonical WiX registry/shortcut paths, native
  per-architecture viewer jobs, absence of QEMU from the viewer path, bounded
  image jobs, final manifest assembly, and downstream product/release
  dependencies.
- Prove the native amd64 viewer image locally and structurally verify exact
  runner/platform pairing for both architectures; hosted PR/release jobs are
  authoritative for native arm64 build/runtime evidence. Parse all
  workflow/action YAML, compile/link the MSI with pinned WiX, run focused and
  full workflow tests, then finish with literal `./scripts/verify.sh --viewer`
  while preserving the operator `.env` hash.
- Require complete PR CI and exact merged-main CI before annotated immutable
  `v1.2.3-rc.7`. Accept the candidate only after the hosted MSI, both native
  viewer architectures, multi-architecture manifest/SBOM, all packages and
  release assets/checksums, anonymous GHCR pulls, OME-backed product gate, and
  GitHub prerelease publication pass.
- Clean Ubuntu/XOA installation, Nginx Proxy Manager browser access, reboot,
  and repeated OME restart/media recovery remain separate promotion evidence.

## Previous scope - restore the release baseline and publish `rc.6` (2026-07-31)

- Preserve the immutable `v1.2.3-rc.5` tag at commit `72283baf`. Release run
  `30222035324` passed environment validation, migrated Postgres, repository
  verification, the viewer bundle, every CLI/release/launcher artifact except
  the MSI, Linux arm64 and amd64 packages, Ubuntu/Debian/Rocky package
  acceptance, Homebrew generation, all five container publications/SBOMs, and
  the pull-only tagged media/API product gate. GitHub Release creation skipped
  because the MSI job failed, so `rc.5` is not a published prerelease.
- Fix the remaining MSI failure without weakening validation. Pinned WiX 3.14.1
  downloaded and checksum-verified on the hosted Windows runner, but PowerShell
  passed `-dProductVersion=$env:MSI_VERSION` literally to `candle.exe`. Compose
  the WiX definition arguments as explicit PowerShell strings before invoking
  the native tool, and keep ICE validation enabled.
- Restore the viewer's proven release baseline before any successor tag.
  Dependabot merges moved `main` to Next 16.2.12, React/React DOM 19.2.8,
  Node types 26.1.1, ESLint 10.8.0, and TypeScript 7.0.2. Current-main CI run
  `30404788813` failed the pinned runtime-baseline contract and clean viewer
  image construction; `ts-jest@29.4.11` requires TypeScript `<7`. Restore only
  the six guarded versions while retaining unrelated compatible updates, then
  prove the lockfile, clean install, audit, tests, build, and container image.

### `rc.6` test and publication plan

- Extend release-workflow regression coverage to require precomposed
  `ProductVersion`, `SourceDir`, and `ReleaseAssetsDir` arguments and reject the
  inline PowerShell environment-variable form that reached WiX literally.
- Run focused Go workflow/runtime-baseline suites, parse all workflow/action
  YAML, run a clean viewer `npm ci`, dependency-tree validation, production
  audit, lint, Jest, Playwright, Next production build, and a real viewer image
  build. Finish with literal `./scripts/verify.sh --viewer` and confirm the
  operator-owned root `.env` hash is unchanged.
- Require complete PR CI and exact merged-main CI. Only then create the next
  annotated immutable tag, `v1.2.3-rc.6`, and accept it only if the hosted MSI,
  package acceptance, release assets/checksums, anonymous GHCR pulls, and the
  OME-backed pull-only product gate all pass and GitHub publishes a prerelease.
- Clean Ubuntu/XOA installation, Nginx Proxy Manager browser access, reboot,
  and repeated OME restart/media recovery remain separate promotion evidence.

## Next scope - public repository hygiene and first-time install docs (2026-07-31)

- The refreshed remote inventory contains 1,003 branches including `main`:
  943 non-default tips are ancestors of `origin/main`; 59 are not merged and
  include the two open Dependabot PR heads. Delete only the 943 ancestry-merged
  refs after an immediate pre-delete ancestry recheck; preserve `main`, tags,
  all 59 non-ancestor refs, and any newly opened PR head.
- Re-audit the repository from a first-time consumer's point of view after a
  successful public candidate exists. README should lead with what BitRiver
  Live is, supported install choices, prerequisites, a short Docker Desktop
  evaluation path, the Ubuntu 24.04 artifact/package path, post-install stream
  workflow, reverse-proxy/media-port boundaries, and honest release status.
- Replace promotional/generated imagery with actual product and workflow
  screenshots only when their provenance and currentness can be verified.
  Until then, prefer concise diagrams or text over imagery that misrepresents
  the shipped UI.
- Align quickstart, Ubuntu/XOA/Nginx Proxy Manager, production release,
  operations, security, architecture, upgrade/backup, troubleshooting, and
  release-note docs with the same commands and evidence boundaries. Add link
  and wording regressions so stale no-release or source-only guidance cannot
  silently return.

## Current scope - repair the `rc.4` release fan-out (2026-07-26)

- Preserve the immutable `v1.2.3-rc.4` tag at merged commit `e67a9304`.
  Merged-main CI passed, but release run `30220542359` cannot create a GitHub
  Release because three packaging jobs failed. Corrections use the next
  immutable candidate tag, `v1.2.3-rc.5`.
- Provision the Windows MSI toolchain explicitly. `windows-latest` no longer
  contains the hardcoded WiX v3.11 installation, so the release must install a
  pinned compatible WiX release and resolve `heat`, `candle`, and `light`
  through the installed tool path instead of a runner-image assumption.
- Keep packaging tools on the runner platform. The Linux arm64 launcher build
  correctly targets the BitRiver binary to arm64, but that target environment
  leaked into `go install nfpm`, placing an arm64 helper outside the host
  executable path. Build nFPM with explicit `GOHOSTOS`/`GOHOSTARCH`, while
  retaining arm64 only for the shipped binary and package metadata.
- Anchor viewer output to the workspace artifact directory. Viewer lint, 217
  Jest tests, 36 Playwright tests, and the production build passed, but the job
  created its archive under `web/viewer/dist` while `upload-artifact` consumed
  repository-root `dist`.

### `rc.4` failure evidence

- Release environment validation, migrated Postgres, the unified verification
  gate, cross-platform CLI/release binaries, four launcher/signature entries,
  Linux amd64 packages, all five image publications/SBOMs, and the pull-only
  tagged media/API product gate passed.
- Windows MSI job `89842557873` failed before WiX compilation because
  `C:\Program Files (x86)\WiX Toolset v3.11\bin\heat.exe` does not exist on the
  current hosted runner.
- Linux arm64 launcher job `89842558023` signed its target binary, then failed
  with `nfpm: command not found` after target `GOARCH=arm64` affected the host
  tool installation.
- Viewer job `89842557853` passed every check and packaged successfully, then
  failed because producer and upload paths were rooted in different working
  directories.
- Downstream package acceptance, Homebrew generation, and GitHub Release
  creation were skipped; they are not evidence from `rc.4`.

### Test and publication plan

- Add workflow-contract regressions that require pinned MSI tool provisioning,
  host-scoped nFPM installation, and identical workspace-root viewer
  producer/upload paths.
- Parse every workflow/action YAML file, run the focused release and CI contract
  suites, run `git diff --check`, then run literal
  `./scripts/verify.sh --viewer`.
- Require complete pull-request and merged-main CI before tagging
  `v1.2.3-rc.5`. Monitor every release job; accept the candidate only after
  package acceptance, checksums/signatures/assets, anonymous GHCR access, and
  the pull-only media/API product gate all pass.
- Clean Ubuntu/XOA, Nginx Proxy Manager browser access, reboot, and OME
  restart/media recovery remain separate promotion evidence.

## Current scope - repair the `rc.3` release fan-out (2026-07-26)

- Preserve the failed `v1.2.3-rc.3` tag at `c9d5a9f3`. The tag workflow
  published the five first-party candidate images, but no GitHub Release was
  created because artifact and pull-only acceptance jobs failed. Corrections
  use the next immutable tag, `v1.2.3-rc.4`.
- Keep cross-compilation and host-side binary inspection separate. Target
  `GOOS`/`GOARCH` remain scoped to `go build`; the Go verifier must compile for
  the Ubuntu runner so it can inspect Windows, macOS, and arm64 binaries without
  attempting to execute a foreign verifier.
- Quote the Windows production modfile as one PowerShell argument. The MSI job
  currently passes the literal text `$env:PRODUCTION_MODFILE` to Go, which Go
  rejects because it does not end in `.mod`.
- Emit the current Cosign v3 bundle format and publish that bundle beside each
  launcher binary. Cosign 3 ignores the deprecated `--output-signature` flag,
  leaving an empty bundle path and failing every launcher matrix entry.
- Package the viewer's built output without assuming an optional `public/`
  directory exists. Preserve `.next`, remove its cache, and copy `public/` only
  when present.
- Bridge an explicitly supplied smoke env to the canonical root `.env` only
  when the root file is absent, because Compose service-level `env_file:
  ../.env` resolution is independent from `docker compose --env-file`. Track
  ownership and remove only the smoke-created bridge during cleanup; never
  rewrite or remove an operator-owned root `.env`.
- Keep secret/evidence publication atomic on Windows Docker Desktop as well as
  Linux runners. Open the temporary descriptor under its closing context before
  applying POSIX-only `fchmod`, skip that syscall where unavailable, and retry
  only transient sharing/permission failures from the final same-directory
  `os.replace`. Retain mode 0600 output where supported and remove the
  temporary file on terminal failure.
- Make SRS readiness valid for both source-build and pull-only deployments.
  Production pull mode intentionally selects the pinned upstream SRS digest,
  while source mode builds a wrapper that happens to add `curl`. Replace the
  curl-dependent SRS probe with a bounded Bash `/dev/tcp` HTTP status check
  against `/api/v1/versions`, and keep the Compose and Helm health contracts
  aligned.
- Preserve production-secure session cookies while allowing the Docker product
  harness to authenticate over its internal HTTP hop. The loopback candidate
  correctly marks `bitriver_session` as `Secure`, so Python's cookie jar stores
  it but will not return it to `host.docker.internal`. For same-origin requests
  only, use the captured session value as the API's supported Bearer token
  fallback; never attach it to an absolute cross-origin URL.
- Resolve third-party release dependencies once per tag workflow. The current
  environment preflight and pull-only product gate independently resolve the
  same eight mutable tags, wasting registry quota and allowing the two gates to
  select different manifests. Preflight must publish sanitized, immutable
  reference/digest evidence; the product gate validates and consumes that exact
  artifact while keeping first-party post-publication evidence separate.

### `rc.3` failure evidence

- Release run `30217498138` passed release-env validation, migrated Postgres,
  the Go verification gate, all five multi-architecture image publications,
  SBOM generation, and Linux amd64 CLI/release-artifact builds.
- Every non-Linux-amd64 CLI and release-artifact job failed with `exec format
  error` while `go run ./cmd/tools/verify-production-binary` inherited the
  target platform. This is one host-versus-target environment defect.
- The MSI job generated `go.production.mod`, then failed because
  `-modfile=$env:PRODUCTION_MODFILE` reached Go literally.
- Four cross-target launcher jobs failed at the same foreign host-verifier
  execution. Linux amd64 reached signing, then failed when Cosign 3.0.6 ignored
  `--output-signature` and attempted to create a bundle at an empty path.
- Viewer lint, unit/integration tests, and production build passed; packaging
  alone failed at `cp -R public` because the viewer has no `public/` directory.
- The anonymous pull-only product gate resolved the published images and
  enforced third-party digests, then failed before startup because the clean
  runner had no root `.env`. GitHub Release creation was skipped, so `rc.3`
  must not be presented as a published candidate.
- The first local Docker Desktop pull-only rehearsal resolved the public
  `rc.3` image manifests, then exposed that Windows Python has no `os.fchmod`.
  The exception occurred before `os.fdopen` owned the descriptor, so the leaked
  handle also made cleanup appear as a sharing violation. No private root env
  was moved or changed. Close ownership must begin before the POSIX-only mode
  operation, with the mode operation conditional on platform support.
- After the Windows helper repair, the local pull-only rehearsal passed the
  former root-env failure, anonymously pulled every digest-pinned image, and
  started the stack. It then proved the raw upstream SRS image has neither
  `curl` nor `wget`; SRS answered `/api/v1/versions`, but its curl-based
  healthcheck remained unhealthy until the bounded gate failed. The pinned
  image does provide Bash, and an ephemeral real-image `/dev/tcp` HTTP probe
  returned status 200.
- With the curl-free probe applied, the next local pull-only rehearsal made SRS
  healthy, started every service, and reached the real media harness. Account
  signup succeeded, but the first authenticated `/api/status` request returned
  `401 missing session token`: production correctly emitted a Secure session
  cookie while the trusted Docker client reached the API over plain internal
  HTTP. The harness needs a same-origin Bearer fallback, not a weaker production
  cookie policy.
- After the same-origin session repair passed focused tests, the immediate
  rehearsal retry was blocked before startup by Docker Hub `429 Too Many
  Requests` while resolving `ossrs/srs:v5.0.185`. Release preflight already
  resolves all eight third-party tags, but the downstream product job repeats
  that work on a fresh runner. Carrying immutable dependency evidence between
  jobs removes this avoidable quota and time-of-check/time-of-use seam.

### Risks

- A syntactically green workflow does not prove cross-platform binaries,
  signatures, packages, or MSI output. `rc.4` must complete the entire remote
  matrix and downstream package acceptance before publication.
- Copying a release env into the checkout can expose generated credentials if
  it survives cleanup or becomes an uploaded artifact. The bridge is
  mode-restricted, ownership-tracked, removed on every exit path, and excluded
  from evidence.
- A session fallback must not turn into credential forwarding. Restrict it to
  the exact configured API origin, preserve explicit Authorization headers, and
  cover both same-origin and cross-origin requests in a local HTTP regression.
- Dependency evidence must remain non-secret and candidate-specific. Validate
  its schema, require every expected env key/reference, reject duplicate or
  unexpected entries and malformed digests, scan it with the existing sentinel
  guard, and never allow it to substitute for first-party anonymous GHCR proof.
- Cosign bundles change the released filename/verification contract from the
  historical `.sig` assumption. Workflow tests and operator release
  documentation must agree on the `.sigstore.json` artifact.
- The `rc.3` images exist even though the release failed. They remain immutable
  failure evidence and are not retagged as `rc.4` or `latest`.

### Test and publication plan

- Add focused workflow-contract regressions for host-scoped verifier execution,
  PowerShell modfile quoting, Cosign bundle output, optional viewer assets, and
  root-env bridge ownership/cleanup.
- Add a unit regression that injects transient atomic-replace failures and
  proves eventual success, plus a terminal-failure check that leaves neither
  final nor temporary secret material behind.
- Add Compose/Helm contract coverage that rejects curl-dependent SRS probes and
  requires the same built-in Bash HTTP status check in both deployment shapes.
- Add a functional Python HTTP test proving a Secure signup cookie authenticates
  the next same-origin request through Bearer fallback while an absolute
  cross-origin request receives no session credential.
- Add release-helper and workflow regressions proving preflight resolves one
  complete third-party dependency set, uploads sanitized evidence, and the
  pull-only job downloads and validates it instead of resolving mutable tags
  again.
- Run focused Go script tests, release-candidate Python tests, Bash syntax,
  workflow YAML parsing, CI/workflow policy checks, and `git diff --check`.
- Run literal `./scripts/verify.sh --viewer`, preserving and hashing the
  operator's private root `.env`, then require the complete pull-request CI
  matrix.
- Merge only green evidence, tag `v1.2.3-rc.4`, and monitor every release job.
  Accept the candidate only after GitHub Release assets/checksums, anonymous
  GHCR access, package acceptance, and the pull-only product gate pass. Clean
  Ubuntu/XOA, Nginx Proxy Manager browser access, reboot, and OME restart/media
  recovery remain separate promotion evidence.

## Current scope - branch hygiene and release CI consolidation (2026-07-26)

- Reduce the remote branch inventory without risking active or unincorporated
  work. Delete only branches whose tips are ancestors of `origin/main`; retain
  every non-ancestor branch until its pull-request history and remaining diff
  are classified separately.
- Keep `ci.yml` as the only automatic pull-request/main orchestrator. Make the
  targeted manual workflows reusable, then have CI call those definitions
  instead of maintaining inline copies for viewer, image scan, shell, docs,
  monitoring, workflow-policy, wizard, and quickstart-entrypoint checks.
- Keep intentionally distinct heavyweight paths separate: the unified Ubuntu
  `test-all` gate owns changed-path integration/Compose work, the production
  golden-path workflow owns full media acceptance, and the standalone Go gate
  owns manual full-matrix/govulncheck coverage.
- Make the tag workflow call the reusable Postgres workflow rather than owning a
  second service definition. Keep host Go verification offline, but restore the
  public Go proxy/checksum database only for the release verification step so
  clean Compose image builds can resolve `go.production.mod`.
- Centralize runtime setup in the local setup actions without a hidden second
  checkout. Every workflow remains responsible for one explicit, SHA-pinned
  checkout before invoking a setup action.
- The reusable quickstart entrypoint matrix must invoke the shared Go setup
  after checkout. `quickstart.ps1 -ValidateOnly` compiles and runs the real CLI,
  so accepting whatever Go version happens to be preinstalled on each runner
  makes the Windows/macOS results nondeterministic. The optional full Compose
  smoke job must use the same setup before invoking its source-driven wrapper.
- Do not create `v1.2.3-rc.3` until focused workflow regressions, the complete
  local gate, the pull-request matrix, and a post-merge targeted workflow run
  are green. Failed `rc.1` and `rc.2` tags remain immutable.

### Audit evidence and assumptions

- The fetched remote contains exactly 1,000 branches: 943 non-default branch
  tips are ancestors of `origin/main`, while 57 are not. GitHub reports no open
  pull requests and no protected branches. `main` and every non-ancestor branch
  are excluded from the first cleanup pass.
- The repository registers 13 workflow files plus GitHub's Dependabot workflow.
  Only `ci.yml` automatically runs for pull requests/main; the other CI
  workflows are manual and/or reusable, and `release.yml` is tag-only.
- Proven drift exists today: `ci.yml` embeds Trivy 0.70.0 with bounded download
  retries while the standalone image workflow embeds 0.50.1 without them.
  Release also duplicates the reusable Postgres service, which already caused
  `rc.1` to diverge, and both setup composite actions perform a second checkout
  using a different action pin than their callers.
- The first consolidation PR run proved GitHub accepts the reusable-call graph
  and executes called docs/policy/image jobs, but showed that a reusable
  workflow edit did not select its own path-gated CI job. Each reusable workflow
  and setup action must therefore be an explicit input to the checks it owns.
- Once selected, the dormant monitoring job exposed a stale container command:
  the pinned Prometheus/Alertmanager images use server binaries as their
  entrypoints, so passing `promtool`/`amtool` after the image makes the server
  parse them as invalid arguments. Select `/bin/promtool` and `/bin/amtool`
  explicitly and prove both real pinned images before accepting the job.
- Prometheus config validation must also represent the runtime file contract
  without using a real metrics credential. Create a private validation-only
  token in the existing temporary directory, mount it read-only only for
  container `promtool check config`, rewrite only the two runtime file paths in
  a temporary config for native `promtool`, and remove all fixtures through the
  existing exit trap. Mount the config, rules, and token as separate read-only
  files at their real runtime paths; a read-only parent-directory mount cannot
  accept a nested token mount and does not reproduce the Compose rules path.
  On Docker Desktop through Git Bash, normalize only bind sources with
  `cygpath` and disable automatic argument conversion for each `docker run`;
  broad `/etc/...` exclusions also suppress conversion of the containing mount
  argument and can silently validate the image-default config instead.
- Keep the rendered Alertmanager config mode 0600. Its container validator must
  run as the invoking host UID/GID so the bind remains readable without making
  a potentially credential-bearing render group- or world-readable.
- Export the renderer's six fallback webhook variables after assigning them.
  Both supported substitution engines consume the process environment, so
  unexported shell defaults otherwise become empty URLs/tokens and produce an
  invalid clean-runner config.
- Monitoring's final Compose overlay render must use
  `deploy/.env.example` explicitly. Clean GitHub runners do not have the
  operator-owned root `.env`, so relying on Compose's implicit env discovery
  makes the reusable validation nondeterministic and cannot prove the
  repository-owned contract.
- Because service-level `env_file: ../.env` is resolved independently from
  Compose's interpolation `--env-file`, create a mode-0600 root validation copy
  from `deploy/.env.example` only when `.env` is absent. Track ownership and
  remove only that validator-created file on every exit; never rewrite or
  delete an existing operator `.env`.
- The `rc.2` release failure is bounded to the `go-tests` verification step:
  job-level `GOPROXY=off` is inherited by Compose build arguments, while
  `verify.sh` already applies offline variables directly to host Go tests.
- Post-merge standalone Go run `30215317804` exposed the same network-scope
  leak in `go-unit-tests.yml`: its Ubuntu `verify.sh` step inherits job-level
  offline settings into clean Docker builds. Restore the public proxy/checksum
  database only for that verification step, matching the repaired release
  workflow while leaving direct host Go steps offline.
- The same run passed macOS Go tests, then failed the generated Helm asset
  drift check because `deploy/helm/bitriver-live/files/srs.conf` predates the
  canonical forwarding-boundary comments in `deploy/srs/conf/srs.conf`.
  Regenerate through `scripts/sync-helm-deploy-assets.sh`; do not hand-edit the
  generated Helm copy or change canonical SRS behavior.
- Merged-main standalone run `30215961551` proved the network repair on Ubuntu
  and the regenerated asset on macOS, but Windows still reports SRS drift.
  Both SRS files currently have unspecified Git EOL attributes; on Windows the
  checked-out generated copy becomes CRLF throughout, while the sync script
  builds its generated header with LF before appending the checked-out
  canonical content. Pin both the canonical and generated SRS paths to LF in
  `.gitattributes` and add a regression test for that repository invariant.
  Do not weaken byte-for-byte generated-asset validation.
- Explicit PR-head run `30216357352` proves that LF fix on Windows, then exposes
  a later independent failure: Git Bash writes POSIX-style `/d/...` artifact
  paths into the govulncheck scan index and passes the same form to native
  Windows Python. `pathlib` interprets those as `\d\...` on the current drive.
  Convert Bash paths at the Python boundary with `cygpath -w` when available,
  including scan-index entries and every Python input/output argument; preserve
  unchanged POSIX paths elsewhere and keep the vulnerability policy blocking.
- User-owned untracked deployment notes/helpers/data and the private root
  `.env` remain outside this change and must not be staged, rewritten, or
  included in branch-cleanup evidence.

### Risks

- Squash merges do not make a historical head an ancestor of `main`. This is why
  the 57 non-ancestor branches are retained during the ancestry-safe cleanup
  even when their work may already be represented on `main`.
- Remote deletion of all 943 ancestry-merged branches is intentionally pending
  a separate explicit confirmation after the execution safety gate rejected the
  broad mutation. No branch was deleted; CI/release work can proceed
  independently from the preserved cleanup classification.
- Reusable-workflow calls change the displayed check hierarchy. There are no
  protected-branch required-check rules today, but the complete PR run must
  prove all path-gated jobs still execute or skip as intended before merge.
- A called workflow cannot elevate caller permissions. The image scan caller
  must explicitly retain `contents: read` and `packages: read`.
- Consolidation must not make CI repeat the same Docker lifecycle. The CI call
  to quickstart smoke will run entrypoint checks only because the unified Ubuntu
  gate already owns Compose smoke for relevant changes.
- A green source/CI candidate still does not prove clean Ubuntu/XOA install,
  Nginx Proxy Manager browser access, reboot recovery, or OME recovery. Those
  remain stable-promotion evidence after tagged artifacts exist.

### Test and publication plan

- Add workflow-contract tests requiring CI reusable calls, one checkout per
  workflow path, release reuse of Postgres, explicit migrations in the reusable
  service, and release verification proxy/checksum restoration.
- Update existing image/viewer/release tests to inspect the single source of
  truth instead of requiring duplicated commands in `ci.yml`.
- Parse every workflow/action YAML file, run `check-ci-contract.sh`,
  `check-go-workflow-config.sh`, focused Go script tests, shell syntax, and
  `git diff --check`.
- Run literal `./scripts/verify.sh --viewer` while preserving/restoring the
  operator's private root `.env`, then push a small PR and require the complete
  remote CI result.
- After merge, manually dispatch the reusable Postgres and other release-relevant
  targeted gates as needed. Only then tag `v1.2.3-rc.3`, monitor every release
  job, inspect checksums/packages, prove anonymous GHCR pulls, and run the
  Docker Desktop pull-only product gate before clean Ubuntu/XOA acceptance.

## Current scope - first public release-candidate publication gate (#1297, 2026-07-24)

- Make the tag-triggered release workflow runnable from this public repository without preloading deployment credentials. Generate strong job-local validation credentials, retain only sentinel-scanned status evidence, and keep real operator secrets entirely outside GitHub Actions.
- Publish first-party images to the repository owner's real GHCR namespace, exposed through one deployment-contract variable so official installs default to `ghcr.io/prohibitedtv` while forks and mirrors can override it.
- Validate release tags as SemVer, normalize package/MSI versions separately from the human tag, mark hyphenated tags as GitHub prereleases, and prevent prerelease tags from moving `latest`.
- After all five tagged images publish, run the canonical Compose stack in production/pull mode and execute the same 1080p media/API golden path. The GitHub Release job must depend on this scanner-approved pull-only evidence.
- Repair the Windows MSI staging/version seam so the release matrix cannot be blocked by paths that disagree with the canonical release asset manifest.
- Only after the workflow change is merged and its PR gates pass, create the first immutable `v1.2.3-rc.1` tag. Treat it as a public candidate for clean Ubuntu/XOA acceptance, not as the stable v1.2.3 announcement.

### Release-candidate design

- Add a deterministic release-env preparation helper that copies `deploy/.env.example`, replaces sample credentials with cryptographically random job-local values, applies the exact release tag/official namespace, resolves current third-party registry digests, and writes a separate temporary sentinel file. It must never print values.
- Extend the quickstart smoke through explicit `BITRIVER_SMOKE_*` controls. Existing local/CI callers keep build/development defaults; the release job supplies an external env file and selects pull/production without rewriting a checkout-owned `.env`.
- In pull mode, skip every Compose build, pull all rendered image references, enforce production dependency digests, render OME with the tagged helper image, run the existing service checks, then run `test-production-golden-path.sh --stack running`.
- Upload only `production-golden-path.json` after the existing evidence scan. Raw Compose logs, generated OME/SRS files, env files, cookies, stream keys, and registry credentials remain runner-local and are removed on every exit path.
- Derive `release_version` and `prerelease` once from the tag. Use the normalized numeric core for MSI, SemVer components for Linux packaging, the original tag for filenames/image tags, and the prerelease flag for GitHub Release metadata.
- Stage the Windows launcher from `deploy/install/release-assets.txt`, and make WiX source paths match the staged `share/bitriver-live` layout rather than maintaining a second incomplete asset list.

### Release-candidate risks

- `ghcr.io/bitriver-live` is not an owned GitHub account namespace, while the repository owner is `ProhibitedTV`; the current workflow cannot publish the references Compose names. Changing the official default is a deployment-contract change and must update Compose, env, CLI preflight, Helm/docs, and generated contract evidence together.
- GHCR packages may require a one-time public-visibility action after their first push. The workflow must prove anonymous manifest access before creating a GitHub Release; if visibility cannot be changed with the repository token, stop with the exact external action rather than publishing unusable assets.
- Tag workflows publish immutable external state. A failed `v1.2.3-rc.1` is never force-moved or overwritten; corrections use `rc.2`.
- The immutable `v1.2.3-rc.1` run reached release validation but stopped
  before artifact/image publication because its fresh Postgres service database
  was not migrated. `test-postgres.sh` intentionally requires
  `BITRIVER_TEST_POSTGRES_RUN_MIGRATIONS=1` for an externally supplied DSN;
  both the tagged and reusable Postgres workflows must opt in explicitly.
- The immutable `v1.2.3-rc.2` run crossed the Postgres gate, then stopped
  before builds because the release Go job's host-only `GOPROXY=off` policy
  leaked through Compose into clean Docker builds. Keep host Go tests offline,
  but give the release verification step the real upstream proxy/checksum
  settings already used by artifact builders.
- Multi-architecture image publication is slower than registry index propagation. Use bounded manifest retries before the pull-only gate, not unbounded sleeps.
- Production mode currently requires third-party digest pins but not first-party pins. Resolve and record first-party manifest digests in candidate evidence/release notes; do not claim digest-pinned clean-host proof from tag-only pulls.
- The existing Windows workflow passes `v...` directly to WiX and stages files under paths WiX does not read. Static checks are insufficient; the remote Windows MSI job remains required before candidate publication.
- The Jul 24 GitHub Advisory Database update for
  `GHSA-mh99-v99m-4gvg` marks every `brace-expansion` release through 5.0.7
  vulnerable to attacker-controlled memory exhaustion and names 5.0.8 as the
  patched release. Viewer CI installs older transitive majors through
  ESLint/Jest tooling, and ordinary `npm audit fix` cannot update those parent
  ranges without a breaking ESLint major. Use one explicit npm override to
  5.0.8 only if clean `npm ci`, lint, unit, browser, build, and audit all pass;
  do not accept `--force` or suppress the advisory.
- GitHub-hosted pull-only proof still is not a clean Ubuntu/XOA install, Nginx Proxy Manager/browser test, or host reboot. Those remain explicit #1297/#1304 promotion gates.

### Release-candidate test plan

- Unit-test tag parsing, prerelease/latest behavior, env replacement, secret uniqueness, sentinel separation, digest formatting, no-value output, and failure on malformed tags or unresolved images.
- Add workflow-contract tests requiring job-local credentials, the official/overridable GHCR namespace, the post-publish pull-only product job, scanner-approved artifact upload, stable-only `latest`, prerelease metadata, and release-job dependency ordering.
- Add a workflow-contract regression requiring every CI-owned fresh Postgres
  service DSN to opt into repository migrations, then run the real
  Postgres-tagged suite against a disposable service database before `rc.2`.
- Add a release-workflow regression requiring the verification step to restore
  production dependency network settings before Docker builds while
  `verify.sh` continues to force host Go tests offline.
- Add quickstart regression tests proving build/development remains the default and pull/production performs no build while enforcing the supplied external env/digest contract.
- Run shell syntax/ShellCheck, generated contract checks, focused Go/Python tests, Compose rendering in both build and pull shapes, release-bundle/package tests, and `git diff --check`.
- Run `./scripts/verify.sh --viewer` and the full PR matrix. After merge, tag the RC, monitor every release job, inspect/download the published assets/checksums, verify anonymous GHCR access and image digests, and run a Docker Desktop pull-only golden path before handing the candidate to the clean Ubuntu/XOA gate.
- For `GHSA-mh99-v99m-4gvg`, require the lock graph to contain only
  `brace-expansion@5.0.8`, `npm audit --audit-level=high` to report zero
  vulnerabilities, and the complete viewer lint/unit/Playwright/build sequence
  to pass after a clean install. Build the real viewer container too, because
  its dependency stage must copy the local compatibility hook before `npm ci`.
  Keep the override registry-backed so nested consumers receive ordinary
  package copies and `npm ls` reports a valid dependency graph.

## Current scope - production golden-path E2E (#1300, 2026-07-24)

- Replace the misleading ingest "E2E" boundary, which currently runs only a storage package test, with one reusable acceptance harness against the real canonical Compose stack: Postgres, Redis, SRS, controller, transcoder, OvenMediaEngine, API, and viewer.
- Generate deterministic 1080p video plus audio at runtime; publish it over the creator-facing RTMP path; require the channel to transition live and back offline; and prove both OME LL-HLS and transcoder HLS are decodable and advancing rather than checking only HTTP status.
- Exercise real self-signup/session cookies, first-channel creator bootstrap, chat send/history, an owner moderation action, multipart VOD upload/transcode/publication, viewer metadata, and health/readiness/status surfaces.
- Emit a versioned machine-readable stage report, media probe evidence, endpoint summaries, timing, and failure context without retaining credentials. Scan retained evidence against per-run sentinel values before accepting it.
- Make the live-stack tier reusable from source quickstart, release workflows, and an already-running clean-host installation. Keep the cheap storage integration test separate so local unit/integration commands do not accidentally claim production coverage.

### Golden-path design

- Implement the product exercise as a standard-library Python harness plus a small shell entrypoint. The harness talks only through public HTTP/RTMP surfaces and invokes host `ffmpeg`/`ffprobe`; it must not reach directly into Postgres or mutate containers to manufacture success.
- The shell entrypoint supports two modes: `--stack running` validates an already-running deployment, while `--stack quickstart` delegates lifecycle to the canonical quickstart smoke and runs the same product assertions before teardown.
- Test credentials and stream keys exist only in process memory or a temporary sentinel file outside the evidence directory. Reports store stable labels, IDs, status, durations, URLs with query/userinfo removed, and redacted command descriptions.
- A phase fails with a bounded, stage-specific error. The wrapper then captures Compose state and selected recent logs through the existing redaction/scanning boundary; success and failure both leave a report that names the first failed stage.
- Keep the first PR tier build-based for deterministic source validation. Tagged RC/stable promotion must call the same running-stack harness after pull-only immutable images are installed; build-mode success alone is not publication approval.

### Golden-path risks

- LL-HLS manifests can exist before media is decodable. Require a media probe and advancing segment/timestamp evidence, not a single successful manifest GET.
- RTMP publication is asynchronous across SRS callbacks, OME forwarding, and transcoder startup. Use explicit per-stage deadlines with last-observed state; do not use unbounded sleeps or retries that hide hangs.
- The first real VOD run exposed that the runtime passed `storeUseCases` into the upload-processing adapter even though that facade does not preserve the repository's upload-recording method. A successful transcode was therefore followed by `upload recording store unavailable`; the unbudgeted persistence retry re-enqueued the whole upload and submitted thousands of duplicate jobs. Wire the concrete repository through a compile-time-complete narrow adapter, retry persistence operations in place with a fixed budget, and never resubmit an accepted transcode merely because recording/update persistence failed.
- The next VOD run exposed two additional contract gaps hidden by handler-only tests: global API authentication rejected the signed `GET /api/uploads/{id}/media` request before its constant-time media-token check, and Compose did not pass the supported `BITRIVER_TRANSCODE_LADDER` setting into the API container. Exempt only the exact signed-media GET route from session auth, keep all neighboring upload routes protected, and add the ladder variable to Compose/env/documentation together.
- Upload FFmpeg failures were also silent because the shared launcher selected log context only from the live-job map. Resolve live or upload metadata under the server lock before constructing the process logger so a failed VOD job remains diagnosable without placing signed source URLs in retained release evidence.
- The authenticated source then proved that `POST /v1/uploads` is asynchronous while `UploadProcessor` treated acceptance as completion: it marked the upload ready and deleted the source before FFmpeg opened it, producing a 404. Add an authenticated upload-job status resource, persist success/failure state, and make the HTTP ingest adapter wait under the processor's existing bounded context. Source cleanup and public readiness may occur only after FFmpeg plus publication complete.
- The final operator probe identified a Docker Desktop/Git Bash harness distortion rather than unhealthy services: MSYS rewrote the exported `/healthz` value to `C:/Program Files/Git/healthz` before Compose passed it into the Linux API container. Disable only MSYS environment conversion for native Docker invocations in the Windows smoke path; retain argument conversion so temporary Compose paths still resolve, then require the aggregate status to pass against the unmodified container endpoint.
- The first Ubuntu CI run exposed an ownership conflict in the new isolated media volume: the image creates `/work` for UID 10001, but the legacy Linux smoke override forced the host runner UID 1001, so the transcoder failed on `mkdir /work/live: permission denied`. Keep host UID overrides only for services that write bind-mounted checkout paths; let the transcoder use its image user with the named volume. Failure diagnostics must also report only sanitized container state rather than dumping `docker inspect` configuration and environment values.
- After Linux quickstart passed, the tagged Postgres tier exposed stale test-only ingest-stub usage: `postgres_ingest_e2e_test.go` still supplied the removed `PlaybackURL` option and expected the superseded OME application create/delete sequence. Align the Postgres repository scenario with the current `OMEPlaybackBaseURL` and application-validation contract, then run the actual `-tags postgres` suite before relying on the remote rerun.
- Multipart VOD source URLs generated from a host request must also be reachable from the transcoder container. Set the request Host to the canonical internal API origin while connecting through the published host port, then verify the returned playback URL externally.
- Live and VOD transcoding are CPU-heavy on hosted runners and Docker Desktop. Use a deterministic short fixture, a release-grade timeout, and measured phase durations; do not lower the 1080p content assertion.
- Raw Compose logs and generated configuration can contain operator credentials. Never put `.env`, generated OME/SRS config, request cookies, authorization headers, or unredacted command lines in the evidence directory.
- Browser playback proof may need a separate Playwright phase after the media/API harness is stable. Do not claim browser recovery/quality behavior from FFprobe evidence alone.
- The current workflow may invoke quickstart and the legacy ingest test in one job. Rewire it so the expensive real-stack path runs once, while the cheap storage test remains independently callable.

### Golden-path test plan

- Add static/unit coverage for report redaction, URL sanitization, timeout/failure stage reporting, deterministic fixture/probe parsing, and workflow wiring.
- Add a runtime-wiring regression plus processor tests that force recording and ready-state persistence failures; require a bounded terminal state and exactly one ingest submission.
- Run the cheap storage ingest integration test separately and prove its name/docs no longer describe it as the canonical product E2E.
- Run the new harness against Docker Desktop from a clean Compose teardown, require real 1080p RTMP publish, SRS live state, OME and transcoder playback probes, chat/moderation, VOD publication/playback, health surfaces, offline transition, evidence scan, and clean teardown.
- Deliberately break at least one dependency input in a focused test and require a failure at the named stage with no secret echoed.
- Run `./scripts/verify.sh`, viewer checks where browser evidence changes, Compose config validation, the upgraded ingest workflow contract tests, and required remote CI before merge.
- Leave tagged pull-only Ubuntu/XOA repetition, repeated-run flake measurements, and browser player recovery/quality evidence explicitly pending until their direct runs exist.

## Scope
- Advance production blocker #1297 with a clean-host Linux Compose installer that consumes release artifacts only and targets Ubuntu 24.04 LTS x86_64 first.
- Make launcher archives plus `.deb`/`.rpm` packages self-contained for the canonical pull-only stack: CLI/wrapper, Compose/env contract, migrations, renderers/templates, proxy config, systemd integration, and operator docs.
- Install immutable program assets separately from operator-owned configuration and data; provide idempotent install/status/log/upgrade entrypoints plus a safe uninstall that retains data unless destruction is explicitly requested.
- Publish the OME config helper as a multi-architecture release image so a source-free host never needs `ome-config:local` or a Go toolchain.
- Add repeatable artifact-only acceptance that proves package contents, non-root/sudo operation, paths containing spaces, restart semantics, diagnostics, and data-preserving uninstall.
- Document the XOA/XCP-ng VM and Nginx Proxy Manager topology, including public HTTP(S), WebSocket forwarding, trusted proxies, media/firewall ports, and internal-only control services.

## Main reconciliation scope (2026-07-24)
- Reconcile the installer foundation with current `main`, including merged PR #1326 and its proven SRS callback, public RTMP/LL-HLS, same-origin `/live/`, OME application, transcoder, Windows evaluation, and README contracts.
- Resolve workflow and release-asset conflicts by composing both requirements: the Ubuntu artifacts must include every new required media URL, and image scans/quickstart fixtures must continue to render the canonical contract.
- Keep the public documentation truthful before the first tag: installer/package code may be release-ready while GitHub Releases, `.deb`, `.rpm`, and launcher downloads remain unavailable.
- Re-run the installer lifecycle, release bundle/package checks, canonical Compose render, full repository verification, and remote PR gates on the reconciled head before marking #1325 ready.

## Design Decisions
- The production install root is `/opt/bitriver-live`; configuration lives under `/etc/bitriver-live`; durable application/transcoder data lives under `/var/lib/bitriver-live`. Release assets remain replaceable and operator data remains outside package ownership.
- A single systemd unit wraps the canonical Docker Compose stack. Docker retains container restart behavior; systemd provides boot ordering, status, bounded startup, and an operator-visible failure boundary.
- The installer stages but does not silently weaken or bypass production validation. First activation uses the existing `bitriver env init --wizard`, `doctor`, `env validate`, pull preflight, migration runner, quickstart, and health checks.
- Linux packages install the same bundle layout as the launcher archive. Package installation may create directories and the disabled unit, but it must not start with sample credentials.
- `bitriver-ome-config` is a version-matched GHCR image for `linux/amd64` and `linux/arm64`; Compose and image preflight use its release tag/digest just like other first-party services.
- Ubuntu 24.04 amd64 is the declared production target for this change. Debian 12 and Linux arm64 remain provisional until their clean-host evidence passes; release docs must not overclaim them.
- Installer completion means files and service integration are installed. Production readiness additionally requires successful quickstart, OME process health, authenticated OME control-plane access, aggregate API health, and the basic ingest/playback acceptance owned by #1300.

## Assumptions
- Docker Engine and the Compose v2 plugin are installed from Docker's supported repository, or the installer may install them only after explicit operator confirmation.
- The VM has at least 4 vCPU, 8 GiB RAM, 20 GiB free disk, working DNS, and outbound access to GHCR plus pinned third-party registries.
- Nginx Proxy Manager terminates TLS on a separate trusted host or VM and proxies the viewer/API HTTP origin to the BitRiver VM. RTMP, LL-HLS/WebRTC, TURN/relay, and ICE traffic are forwarded directly rather than tunneled through the HTTP proxy host.
- The tagged release publishes all first-party multi-architecture images before clean-host acceptance runs.

## Risks
- Existing launcher packages omit runtime assets and resolve repo-relative paths from the current directory; add an explicit installed asset root and verify the bundle outside the source checkout.
- Compose currently uses local-only OME render images in pull mode; publishing and pinning this helper changes the deployment contract and release workflow together.
- SRS/OME render jobs write generated files into the installed asset tree; use an operator-owned runtime workspace or deliberately writable generated paths without making immutable package files credential-bearing.
- A systemd timeout that is too short can kill a valid first image pull; make it bounded but generous and preserve redacted logs plus exact recovery commands.
- Nginx Proxy Manager handles HTTP/WebSocket traffic but not the full media port surface. Documentation must distinguish proxy routes from XOA/firewall/NAT rules.
- GitHub-hosted CI cannot prove an actual XOA VM reboot. Automate everything repeatable, then leave final tagged-release reboot evidence explicitly pending rather than claiming it.
- Read-only Go dependency and verification checks must stay inside first-party Go roots and must not request VCS build stamping: on Windows-mounted Linux workspaces, an implicit `git status`, `go test ./...`, or a blanket `find .` can livelock on frontend dependencies and media before test timeouts start. Set `GOFLAGS=-buildvcs=false`, scope default Go verification and models-import scans to `cmd`, `internal`, `scripts`, and `web`, and guard these contracts in tests.
- ShellCheck treats single-quoted workflow-contract patterns containing `$out_dir` or `$launcher_root` as suspicious non-expansion. Preserve the intended literal dollar signs with escaped variables in double-quoted fixed-string assertions so CI remains strict without suppressions.
- Image-scan logic is duplicated between `ci.yml` and the manual workflow; a hard-coded legacy `bitriver-live/ome-config:local` reference can diverge from the Compose-built GHCR tag and make Trivy scan a nonexistent image. Resolve the OME helper from the collected Compose image list in both workflows and reject missing or ambiguous matches.
- Compose classifies `ome-health-token-check` as non-buildable even though it shares the locally built `ome-config` image. A blanket `compose pull --ignore-buildable --policy missing` can still make a denied registry request for that sibling service. Enumerate rendered image references, retain any image already inspectable in the local daemon, and pull only genuinely absent runtime images.
- The installer branch and PR #1326 both changed release workflows, deployment variables, quickstart smoke, and operator documentation. A mechanical conflict choice could silently discard either the published OME helper or the verified media-routing contract; reconcile each overlapping file by rendered/runtime behavior, not by branch preference.
- Current `main` correctly says no tagged downloads exist. Do not replace that statement with live download commands until the release workflow has actually published a tag and assets.
- The quickstart smoke may reuse an operator's existing root `.env`, including files created before public RTMP/LL-HLS variables became required. Because the smoke already forces an isolated development/build posture and host ports, it must also supply non-secret smoke defaults for every required public media URL without rewriting the operator file.
- Current `main` merged three incompatible major development-tool bumps: TypeScript 7 is outside the latest `ts-jest` range (`typescript >=4.3 <7`), ESLint 10 is outside the Next.js-bundled lint plugins' declared ranges, and Node 26 types exceed the enforced Node 24 runtime. Restore the proven TypeScript 6.0.3, ESLint 9.39.5, and `@types/node` 24.13.3 pins until the complete toolchain supports the newer majors; do not bypass peer resolution with `--force` or `--legacy-peer-deps`.
- The official npm audit reports three high-severity runtime findings against Next 16.2.10 and its PostCSS/Sharp dependency chain. Upgrade `next` and `eslint-config-next` together to the non-major fixed 16.2.11 release and require the blocking production audit to report no high/critical findings.
- Next 16.2.11 fixes its direct advisories but still hard-pins vulnerable PostCSS 8.4.31 and allows only vulnerable Sharp 0.34.x; no newer stable Next release exists. Temporarily override those transitive packages to fixed PostCSS 8.5.22 and Sharp 0.35.3, lock the exception in the runtime-baseline test, and exercise the production image. The viewer already sets `images.unoptimized: true`, so it does not depend on Next's image optimizer; remove the override once an aligned stable Next release ships.
- The root Docker build context currently includes `deploy/transcoder-data/`; this checkout's local media made the context roughly 200 MB and could leak runtime artifacts to a builder/cache. Exclude that runtime directory explicitly and lock the boundary in a static regression.
- The reconciled CI image scan found CVE-2026-59873 in `tar@7.5.15` under the Node 24 Alpine image's global npm installation, not in the viewer application dependency tree. The production runner needs the Node executable but never invokes npm or npx; remove the package manager payload and entrypoints from the final runtime stage, retain npm in the build stages, and guard both the runtime capability and reduced attack surface in the Dockerfile baseline test.

## Test Plan
- Shell syntax, installer unit tests, and focused Go tests for installed-root discovery, image preflight, Compose invocation, and OME helper image selection.
- Assemble launcher bundles in temporary paths containing spaces and verify every Compose bind mount plus required executable/document exists without reading the checkout.
- Exercise install twice, disabled-before-configuration behavior, status/log commands, upgrade staging, service enablement, and safe uninstall/data purge using isolated filesystem roots.
- Extract `.deb` and `.rpm` payloads and compare them to the canonical bundle manifest; run package install checks in Ubuntu 24.04 and Debian 12 containers where possible.
- Render Compose in pull-only mode, run the existing quickstart smoke with the release-shaped bundle, and require bounded OME/aggregate health diagnostics.
- Run `./scripts/verify.sh`, release workflow contract tests, `git diff --check`, and pull-request CI before publication.
- Re-run default Go verification, architecture, and models-import checks in the pinned Linux toolchain; assert VCS stamping is disabled and package/filesystem traversal is limited to first-party Go roots so mounted-workspace verification remains bounded.
- Run ShellCheck on the corrected release-bundle assertion and rerun its focused test before pushing the CI repair.
- Add a workflow regression guard for Compose-derived OME scan selection, parse both workflow files, and reproduce the image-selection logic against the rendered CI Compose image list before republishing.
- Add a quickstart regression guard against blanket non-buildable pulls, run shell syntax plus the full quickstart smoke locally, and require the unified Ubuntu gate to pass on the same corrected path.
- After merging current `main`, run focused Go workflow/contract tests, `bash -n` on changed shell scripts, the release bundle and Compose-host lifecycle tests, `docker compose ... config --quiet` with a generated production-shaped env, `scripts/test-quickstart.sh`, and `./scripts/verify.sh`.
- Inspect the staged reconciliation for root `.env`, generated OME/SRS output, runtime data, secrets, and the user's unrelated untracked deployment helpers before publication.
- Run the quickstart smoke with a pre-existing env that omits the public RTMP/LL-HLS variables and require Compose interpolation to proceed through the runtime gate.
- Run a clean `npm ci`, viewer lint/test/build, and the viewer Docker build after restoring the supported Node 24 / TypeScript 6 / ESLint 9 development baseline; require the clean install to avoid peer-override warnings.
- Run `npm audit --omit=dev --audit-level=high` after the Next.js patch and record any remaining high/critical production finding as release-blocking.
- Start the clean production viewer image after the security overrides and require its public route to return successfully; do not accept a zero-count audit if the resulting image cannot boot.
- Rebuild the root OME/helper context after updating `.dockerignore` and confirm local transcoder output is neither transferred nor packaged.
- Rebuild the production viewer image, confirm `node` and the Next standalone server remain runnable while npm/npx and `/usr/local/lib/node_modules/npm` are absent, then rerun the blocking image scan and full remote CI.

## Boundaries
- The user explicitly authorized installer, deployment-contract, and roadmap work for the Ubuntu/XOA/Nginx Proxy Manager target, including the necessary release workflow changes.
- Do not edit root `.env`, stage generated OME credentials/config, or include the user's untracked deployment helper files/runtime data.
- Do not expose PostgreSQL, Redis, SRS control, OME Managers API, or transcoder control ports through Nginx Proxy Manager.
- Do not claim Debian, ARM64, host reboot, or real playback acceptance until direct evidence exists.
- Do not automatically delete operator configuration, database volumes, recordings, or transcoder data during package removal.

## Completion
- Local implementation and acceptance are complete: pinned-toolchain repository verification, package generation, PostgreSQL migration acceptance, release-shaped Compose/OME smoke, and clean teardown passed.
- Draft PR #1325 is published. Its ShellCheck failure, stale local-only OME Trivy target, and sibling-service registry pull were repaired with focused Linux bundle/workflow/Go checks plus a full OME/API/viewer host smoke and clean teardown passing. Required CI is green on implementation head `de869492`, including the Ubuntu test-all gate, image vulnerability scan, viewer integration, cross-platform Go, and entrypoint checks.
- The installer candidate is reconciled with current `main` on head `e3f96cb5`. CI run 30102433565 is green across the unified Ubuntu gate, first-party blocking image scan, viewer integration/build/audit, secret/docs/shell/workflow guards, cross-platform Go, and quickstart entrypoint matrix.
- Tagged Ubuntu/XOA reboot, authenticated OME control-plane access, and real ingest/playback/recovery remain required external release evidence for #1297/#1300/#1304; this local candidate does not claim them.