Commit Graph
33 Commits
Author SHA1 Message Date
timothy dbd4bb43f7 Merge pull request 'feat(analyzers): enable incremental latest-All enforcement' (#375) from feat/15-analyzer-promotion into main
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m51s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 6m15s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 7m9s
2026-07-16 20:19:07 +00:00
timothyandClaude Opus 4.8 a2c056dd7a docs(release): prepare v26.9.0 promotion [decisions-edit]
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Consolidate docs/decisions.md (1923 -> 1028) by extracting four cohesive
topic clusters into docs/decisions/ (optimistic-concurrency,
api-auth-security, release-ci-governance, spa-modularization) — content
relocated verbatim (lossless; all rationale + reversals preserved), main
Index rebuilt to reference the topic files plus the remaining in-file
entries, docs/README.md points back at the decisions Index. Add the
v26.9.0 row to the ci-cd.md version table.

refs #340

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:17:23 +02:00
timothyandCodex 10ae885b47 fix(analyzers): preserve non-CPM and image builds
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 17s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 5m12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m52s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 2m40s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m40s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Guard analyzer package references for the inline-version MCP tool and copy the SDK suggestion baseline into the Docker build context before restore.

Refs #15

Co-Authored-By: Codex <codex@openai.com>
2026-07-16 21:52:27 +02:00
timothyandCodex 8f90cea5cc feat(analyzers): enable incremental latest-All enforcement
Centralize SDK and threading analyzers, baseline the .NET 10 All rule inventory at suggestion severity, and promote S3981 repo-wide. Fix the always-true worker count predicate and cover the idle/active branches.

Fixes #15

Co-Authored-By: Codex <codex@openai.com>
2026-07-16 21:52:27 +02:00
timothyandClaude Opus 4.8 e78aa01c05 ci(coverage): exclude generated EF migrations to stop coverage OOM (exit 137)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m26s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 16s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 3m46s
The shared Build & test job OOM-killed (exit 137) with coverage enabled: the
whole-solution Architecture.Tests process instruments both EF migration folders
(~2.59M generated lines vs ~200k authored), ballooning coverlet's in-memory
hit-count arrays past the runner's limit.

Add coverlet.runsettings excluding **/Migrations/*.cs (+ generated/compiler
attributes) and pass it via --settings on the Test step. This cuts the
instrumented surface ~126x (2,510,185 -> 19,963 coverable lines in the
Architecture.Tests process, verified locally) and makes the reported percentage
reflect authored code. Documented in docs/ci-cd.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:03:54 +02:00
timothyandClaude Opus 4.8 2090f7865c ci(coverage): collect code coverage and publish a summary (refs #15)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m29s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (pull_request) Has been cancelled
#15 scope item 4 ("collect with coverlet and publish a summary; decide on
a floor later"). coverlet.collector was already referenced by four of the
six test projects but the CI Test step never passed --collect and nothing
summarized the result.

- Add coverlet.collector to ErsatzTV.Core.Tests and ErsatzTV.Tests so
  coverage is uniform across all *.Tests projects.
- CI Test step now runs --collect:"XPlat Code Coverage" --results-directory
  ./coverage, then a best-effort Coverage summary step merges the per-project
  Cobertura reports with ReportGenerator (TextSummary to the log,
  MarkdownSummaryGithub to the job step summary). No floor is enforced yet;
  continue-on-error keeps it purely informational.
- gitignore /coverage/; document the step in docs/ci-cd.md (test job).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:45:32 +02:00
timothyandClaude Opus 4.8 0d7803079c ci: add advisory functional-E2E curl harness (fixes #299)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m54s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 18s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 4m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m42s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m22s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m38s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 6m2s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m41s
Codify the manual live-E2E curl flows sessions have been re-running by hand
into a CI regression net: a new `functional-e2e` job boots the app from source
(via scripts/e2e-local.sh, parameterized for Release) and drives scripts/
e2e-functional.sh against it.

First-cut contracts (all curl-only, deterministic, no seeded media/ffmpeg/browser):
- legacy->SPA redirect sweep + the /api,/artwork never-redirect exemption
- auth/CSRF/security-stamp flow (setup-claim, read-gate, CSRF, login, logout+revoke)
- library-scan status contract (404/202/scan-status)
- optimistic-concurrency If-Match/412 round-trip

Advisory by design (separate job, not a `build` dependency, not a required
check) so a functional-E2E flake can't block the unit-test gate; promote once
proven, mirroring the migrations-job rollout. SQLite default -> no DB service.

Deferred to #299 follow-ups (need scanner+seeded media or a browser to be
deterministic): the racy 409 re-trigger, playout-build lock 409, Playwright UI
flows.

Assertions verified 30/30 green against a real Release-built instance; caught
/artwork/* returning 400 (not the 404 a static read suggested).

Docs updated same PR: docs/ci-cd.md (new job), docs/e2e-local.md (harness),
docs/decisions.md (append-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 13:49:33 +02:00
timothyandCodex 985eed072c docs(release): prepare v26.8.0 promotion [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m37s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m45s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m49s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m51s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m47s
refs #335

Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 19:50:39 +02:00
timothyandClaude Opus 4.8 366d5d316c feat(ci): #314 — repeatable black-box security scan (authenticated ZAP + semgrep)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 15s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 10m1s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m55s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
scripts/security-scan.sh: boots a THROWAWAY container from the image under test
(fresh config volume; never the deployed prod/test container — the active scan
attacks write endpoints), reads its machine key, and runs an authenticated OWASP
ZAP api-scan that imports /openapi/v1.json (all 160 /api/v1 ops) and injects
X-Api-Key on every request via a replacer rule so it reaches the
[RequiresAuthentication] + RequireKeyForReads surface — then a semgrep SAST
cross-check. Wrapped in `timeout` because zap-api-scan can hang in post-scan
cleanup after the report is already written.

docs/ci-cd.md: new 'Security scanning' section (out-of-ecosystem black-box gate,
run on the docker host per-release like migration-smoke, not a per-PR CI job) +
the Microsoft.OpenApi 2.7.5 pin note in dependency management.

refs #314 #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 08:28:13 +02:00
timothyandClaude Opus 4.8 4da2b67ab2 feat(ci): #315 migration-on-prod-copy smoke for the release path
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 14s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 20s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 20s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m25s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
CI's migrations job only proves a migration is well-formed against a fresh,
empty DB. It never exercises the migration — or ErsatzTV's startup data steps
(DatabaseMigratorService -> DbInitializer + PopulatePathHashes over the real
MediaFile table) — against the accumulated prod SQLite, so a migration green on
a fresh DB can still fail/corrupt on prod, found only mid-deploy.

scripts/migration-smoke.sh rehearses it on a THROWAWAY copy of the latest prod
backup: boots the new image against the copy, gates PASS on the "Done applying
database migrations" log line (the migrator is a BackgroundService running
concurrently with Kestrel, so HTTP readiness alone doesn't prove migrations
finished), FAILs on early container exit / migration exception / timeout / not
serving afterwards. Always operates on a copy, never the live DB; tears down its
container + temp dir (incl. the container's root-owned config files) on exit.

Validated live 2026-07-12: :latest vs a copy of the 283MB prod backup ->
migrations applied cleanly, app booted+served, temp dir removed.

Home split: this repo owns the script + docs; wiring it into the Komodo
pre-deploy step is server-management#589 (cross-repo). Docs: docs/ci-cd.md
(Migration integrity), docs/decisions.md (new entry, pure insertion).

fixes #315

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:18:16 +02:00
timothyandClaude Opus 4.8 6e92a951eb feat(process): #303 decisions.md consolidation size-floor (read-cost, non-blocking) [decisions-edit]
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Failing after 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 15s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (pull_request) Has been cancelled
Timothy's refinement: the between-releases consolidation floor triggers on the file's
READ COST — its line count, i.e. the context an agent burns reading the log — not entry
count. The decisions-guard CI job now emits a non-blocking ::warning:: once decisions.md
exceeds 1800 lines (the point past which it no longer fits one default 2000-line agent
Read). Documented in the decisions.md header, the H9 entry, and ci-cd.md (job description
+ release-checklist note). Touches committed H9 lines, hence [decisions-edit].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 18:23:58 +02:00
timothyandClaude Opus 4.8 59ecafecce docs(process): #303 fold review nits — H3 case-insensitive, granularity wording, newline note [decisions-edit]
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Adversarial review (PR #305, MERGEABLE) nits:
- H3: `grep -iE` so a root `Screenshot.PNG` is caught too (was lowercase-only).
- decisions-guard.sh: comment the trailing-newline assumption (dropping the final
  newline would make git render the next append as a last-line modify -> false-block;
  self-correcting via [decisions-edit], .editorconfig enforces the newline).
- docs: clarify CI is PR-wide (`range`) vs Husky per-commit (`staged`) — shared
  detection logic, deliberately different granularity; local hook is the stricter gate.
  Replaces the slightly-overstated "can't drift" wording. Touches the committed H9
  decisions.md entry, hence the [decisions-edit] token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 18:18:30 +02:00
timothyandClaude Opus 4.8 997f96c1f5 docs(process): #303 append-only supersession + consolidation convention [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
decisions.md header now documents the enforced append-only rule, the `[decisions-edit]`
escape, mark-and-keep supersession (banner on the reversed entry + `(superseded)` TOC
tag), and consolidation at each release. ci-cd.md documents H3/H9 hooks + the
decisions-guard job and adds a "consolidate decisions.md" step to the release checklist.
New decisions.md entry records the H9/H3 mechanization. The header edit modifies existing
lines, so this commit carries the [decisions-edit] token — the guard working as designed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 18:13:13 +02:00
timothyandClaude Opus 4.8 fa2d787ac1 ci: make the MySql migration-apply resilient to concurrent-runner contention
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m12s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m24s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Root cause (diagnosed from run logs 513/515/516): the EF migration-integrity
job's "MySql apply all migrations to a fresh DB" step flakes when two migration
jobs land on the SAME runner host at once — each `services: mysql:8.4` container
starves the other, so the 787-migration replay either exceeds MySqlConnector's
30s default command timeout ("Command Timeout expired", run 513 on ci-runner) or
has its connection dropped mid-replay ("MySqlEndOfStreamException", run 516 on
bumblebee-runner). It's pure infra contention: `has-pending-model-changes` (the
model check) passes both providers, and the identical tree passes on a quieter
host (run 515). Both runners have both passed and failed — not one bad runner.

Fix (runner-agnostic, repo-owned workflow only — no runner-host change needed):
- Raise `DefaultCommandTimeout` to 300s in the MySql connection string.
- Wrap the apply in a 3× retry that resumes from `__EFMigrationsHistory` (EF
  commits each migration in its own transaction, so an interrupted one rolls back
  and the retry continues). A real migration failure fails on every attempt, so
  the retry can't mask a genuine problem.

Docs: ci-cd.md migration-integrity section documents the contention + retry.

Refs #13 #236

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:42:49 +02:00
timothyandClaude Opus 4.8 1b5efd7b9d ci: prod follows :prod (remove version-pin bump-prod-compose job)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 18s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m2s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m56s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m1s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m1s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m24s
Timothy reversed the version-pin decision: prod's media-servers compose now
follows the floating :prod tag, redeployed by Komodo Global Auto Update. The
bump-prod-compose job (#275) rewrote a :<version> pin, which would flip :prod ->
:26.8.0 on the next release — remove it. docs/ci-cd.md reconciled to the :prod
model (+ flags the open caveat: verify Global Auto Update runs the #553
pre-deploy backup, else releases deploy without a backup).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:20:14 +02:00
timothyandClaude Opus 4.8 0ff21d5b8e ci: restore bump-prod-compose auto-deploy job on v* release
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m32s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m44s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Bump prod compose tag (server-management) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 9m59s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 7m9s
Build ErsatzTV Image / Bump prod compose tag (server-management) (push) Has been skipped
The auto-pin-to-prod job designed on the unmerged `ci/auto-bump-prod-compose`
branch (3d6ac883) never landed on main — so v* releases (v26.5.0, v26.6.0) did
NOT auto-bump the server-management compose pin (it sat at 26.5.0). The docs
(homelab-docs Docker/ErsatzTV.md, ci-cd.md) described the auto-bump as if live.

Restore the job verbatim (its credentials already exist: the `ersatztv-ci-deploy`
write deploy key, id 5, on server-management + the SERVERMGMT_DEPLOY_KEY secret
here). On a v* tag, after the test-gated image builds, it rewrites the pinned
`ersatztv:<version>` tag in docker/bumblebee/stacks/media-servers/compose.yaml
and pushes to server-management `master` → the Gitea->Komodo webhook redeploys
prod with a pre-deploy backup. Idempotent (no-op if already pinned).

docs/ci-cd.md updated to match (release procedure + the stale ":prod pin" claim,
which was actually an immutable :<version> pin since 2026-07-07).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 21:44:36 +02:00
timothyandClaude Opus 4.8 f27f458ff5 feat(web): add husky pre-commit/pre-push/commit-msg guardrails
Installs husky git hooks (via web/'s lint-staged + npm, since the JS/TS
project lives in web/ with no root package.json) to catch lint, format,
type, and generated-API-drift errors locally before they reach CI.

Hooks (committed at repo root under .husky/):
- pre-commit: (a) lint-staged runs eslint --fix on staged
  web/src/**/*.{ts,tsx} + a project-wide typecheck; (b) if any *.cs are
  staged, dotnet format --verify-no-changes on just those files (skipped
  when no .cs staged, so web-only commits skip the sln load).
- pre-push: CI-parity gate — cd web && check:api && lint && typecheck &&
  build. Blocks pushing drift or a change that breaks an unstaged file.
- commit-msg: requires a Co-Authored-By trailer (merge commits exempt).

Wiring: web/package.json gains husky + lint-staged devDeps, a lint-staged
config, and a `prepare` script (cd .. && husky) that points git's
core.hooksPath at the repo-root .husky dir on npm install. A fresh
`web/` npm install installs all four hooks automatically.

Monorepo/worktree gotchas handled:
- husky init hard-checks for .git in cwd, so `prepare` cd's to the repo
  root before invoking husky (npm keeps web/node_modules/.bin on PATH).
- git exports GIT_DIR while running hooks; in a worktree/subdir that made
  pre-push's `git diff` (check:api) mislocate the working tree and pass
  silently on drift — pre-push now unsets GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE.

docs/ci-cd.md: new "Pre-commit hooks (web/)" section covering all four.

Verified: eslint error blocks commit; clean commit passes; bad-format .cs
blocks (dotnet format ~6-7s scoped), good .cs passes; check:api drift and
a lint error each block `git push --dry-run`, clean state passes; missing
Co-Authored-By blocks commit-msg, present passes; non-web/.cs commits skip
lint/format. npm run lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 19:31:22 +02:00
timothyandClaude Fable 5 1bef9c9076 ci: small-runner lane + per-ref concurrency (queue starvation fix)
- build & docs-reminder jobs -> runs-on: small (dedicated small-jobs runner,
  server-management#574). Gitea dispatches a job as a runner task even when
  its 'if' skips it; the PR-run skip of 'Build & push image' waited up to
  31 min for an ubuntu-latest slot, stalling every PR run.
- concurrency scoped per event+ref with cancel-in-progress for PRs: runs
  parallelize across PRs, superseded syncs auto-cancel. Previously one global
  group serialized ALL runs (single-runner relic). Main/tag builds still
  serialize within their ref; don't push main + v* tag simultaneously
  (shared :buildcache / smoke container) — tag after main is green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:40:30 +02:00
timothyandClaude Opus 4.8 6bdb7dae1c docs+ci: harden the docs-first rule + add a non-blocking parity-doc reminder
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m4s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 11m56s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
CLAUDE.md Conventions: turn "read docs before exploring" into a hard rule with red-flag
framing (do NOT reverse-engineer conventions from source; applies to subagents too) and a
"which doc to update in the same PR" table. Add a lightweight, PR-only, NON-BLOCKING
`docs-reminder` CI job that warns when a screen/route change skips
docs/blazor-route-parity.md. No dotnet/node in that job (no actions/cache), so it can't hit
the VM-127 cache-save issues (server-management#570). Documented in docs/ci-cd.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 22:17:25 +02:00
timothy 35e5799cd6 docs(ci-cd): runner relocated off bumblebee to ci-runner VM 127 (server-management#570)
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Failing after 16m15s
Build ErsatzTV Image / Build & test (.NET) (push) Failing after 37m23s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Update stale jazz/bumblebee runner references: concurrency note, amd64
host note, EF-migration mysql-service network (now per-job, not
downloadswarm), and registry insecure-registry host.
2026-07-08 19:09:49 +00:00
timothyandClaude Opus 4.8 e68aa5834c ci: speed up build pipeline (NuGet cache, shallow checkout, jar layer reorder)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m3s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Three CI wins from ersatztv#190, all config-only:

- Cache ~/.nuget/packages via actions/cache@v4 in both the `test` and
  `migrations` jobs, keyed on Directory.Packages.props + global.json, so
  dotnet restore no longer starts from scratch every run.
- Shallow-checkout the `test` job (fetch-depth: 1) since it never runs
  git describe/log; only `build` needs full history for versioning. The
  `migrations` job's checkout was already effectively shallow (default
  fetch-depth 1), documented explicitly.
- Reorder the Dockerfile so the openapi-generator jar wget happens before
  the openapi spec COPY, decoupling the ~30MB download layer from spec
  changes (e.g. v1.json) that previously busted it on every API PR.

Documented all three in docs/ci-cd.md alongside the existing job/Dockerfile
notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:37:31 +02:00
timothy e170b2dc3c fix(ffmpeg): align transcode pipelines with ffmpeg 8
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
refs #9
2026-07-01 19:37:51 +02:00
timothyandClaude Opus 4.8 2edf3dab49 ci: assert key IPTV endpoints in the container smoke test (#16)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m4s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m5s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 5m46s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m47s
Extend the image smoke test from "serves HTTP" to a real E2E: after HTTP readiness,
assert the Jellyfin-facing surfaces on the freshly built image — /iptv/channels.m3u
returns 2xx containing #EXTM3U, and /iptv/xmltv.xml returns 2xx containing a <tv root.
xmltv.xml needs channels.xml (the scheduler writes it a few seconds after boot), so each
endpoint is polled with a deadline; container logs are dumped on failure. Catches
routing / base-URL (#1) / migration regressions that leave the app "up" but serving
broken playlists/guides.

Validated the exact checks against the real :latest image on a fresh container (both 200
with the expected shape) before committing.

Refs #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:22:47 +02:00
timothyandClaude Opus 4.8 307da32f34 ci: EF migration integrity checks for SQLite + MySql (#13)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 3m56s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m59s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 5m26s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m33s
New `migrations` job in docker-build.yml runs on every push/PR and, per provider:
- `dotnet ef migrations has-pending-model-changes` — fails on model drift (an entity
  changed without a matching migration).
- `dotnet ef database update` against a fresh empty DB — applies all migrations,
  catching broken/un-orderable ones.

SQLite (the prod provider) uses a throwaway file; MySql uses ServerVersion.AutoDetect
(connects at config time), so the job runs a `services: mysql:8.4` container (the
act_runner uses Docker execution on network downloadswarm, so the service is reachable
as `mysql:3306`). Independent gate for now (not a `needs:` of the image build) so the
MySql-service dependency can't block image builds until proven stable.

Validated both providers locally against real DBs: SQLite 787 migrations -> 139 tables;
MySql 8.4 305 migrations -> 137 tables; both model-drift checks clean.

Docs: ci-cd.md "Migration integrity" (dual-provider discipline, EF CLI pattern, the
non-transactional caveat) + CLAUDE.md convention.

Refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:58:41 +02:00
f2d0a36ed6 build: adopt static-analysis packs at suggestion (#15, increment 1)
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 2m19s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m31s
Add Roslynator, SonarAnalyzer.CSharp, Meziantou.Analyzer, and AsyncFixer as central
analyzer references (Directory.Build.targets, CPM-versioned, guarded on
ManagePackageVersionsCentrally so the gitignored .mcp tool isn't affected).
.editorconfig defaults dotnet_analyzer_diagnostic.severity to `suggestion` so the packs
surface findings without failing the TreatWarningsAsErrors (TWAE) build; rules are
promoted to warning/error incrementally (promotion = enforcement via the existing TWAE
build, so no separate lint step is needed).

StyleCop.Analyzers is intentionally excluded: its latest stable (1.1.118) crashes
(AD0001) on C# records and overlaps the existing .editorconfig/Roslynator.

Blazor .razor: editorconfig severity overrides don't reach analyzer diagnostics in Razor
@code (source-generator limitation; dotnet format can't fix them either), so the
currently-firing SonarAnalyzer rules are temporarily NoWarn-ed in ErsatzTV.csproj and
burned down rule-by-rule in #25. The same rules run at suggestion on .cs.

Formatting normalization (mixed UTF-8 BOM + whitespace) is deferred to its own PR.

Full-solution Release build green (0 errors, 0 warnings).

Refs #15

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 07:37:15 +00:00
timothyandClaude Opus 4.8 f1ff39954f docs(renovate): Dockerfile manager now enabled (HTTP registry) [skip ci]
The previously-deferred dockerfile manager is live: it manages docker/Dockerfile
(mcr dotnet bases + internal ersatztv-ffmpeg) and reads the HTTP-only Gitea
registry via a RENOVATE_HOST_RULES host rule (insecureRegistry + read creds) set
in the workflow env. Vestigial arm/ffmpeg-tests Dockerfiles excluded; compose
manager unused (build-only).

refs server-management#484

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:22:08 +02:00
timothyandClaude Opus 4.8 74815da024 docs: NCalcSync/SQLitePCLRaw advisories resolved (#8) [skip ci]
Update ci-cd.md: the scheduled vuln scan is green now that #8 cleared the
advisories (red = a new one); note the NU1901-1903 demotion is kept by design
(criticals still block; lower severities surface via the scan + Renovate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 02:17:55 +02:00
timothyandClaude Opus 4.8 97bf50cb5b docs(renovate): document live Renovate setup [skip ci]
Renovate is now live (PR #18/#19): self-hosted weekly Gitea Actions job that
opens dependency-update + OSV vuln-fix PRs and a Dependency Dashboard, with
patch-level auto-merge scoped to test/dev-only packages. Document it under
docs/ci-cd.md → Dependency management, plus a CLAUDE.md conventions pointer.

refs server-management#484

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 01:57:11 +02:00
timothyandClaude Opus 4.8 d9cdff8154 docs: record Central Package Management + scheduled vuln scan (#14) [skip ci]
- ci-cd.md: new "Dependency management" section (CPM, NuGet audit, scheduled
  dependency-scan.yml); Dockerfile notes now list Directory.Packages.props and
  why it's required before restore.
- CLAUDE.md: dependency convention (edit central Directory.Packages.props, never
  re-add Version=); correct the test framework note (NUnit, not xUnit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:33:44 +02:00
timothyandClaude Opus 4.8 0e5e6ebc72 docs: note Gitea drops runs on rapid back-to-back pushes [skip ci]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:44:23 +02:00
timothyandClaude Opus 4.8 c7aad4689e docs: enshrine the fork versioning scheme
Document upstream's vYY.<release-seq>.<patch> scheme (year, sequential
release-in-year, patch) in docs/ci-cd.md + CLAUDE.md so we follow it going
forward: <release-seq> is NOT the calendar month (v25.2.0 shipped in June,
v26.3.0 in Feb), it's a per-year counter that resets each January. v26.3.1
= our infra-only rebuild of upstream 26.3.0; v26.4.0 reserved for the first
release with app changes. Also notes the [skip ci]-on-a-tagged-commit gotcha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:29:13 +02:00
timothy 45627ede82 ci: harden build pipeline per adversarial review
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 1m55s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 24s
- Add concurrency group so the single jazz runner can't run the
  push-main-then-push-tag release flow in parallel (shared :buildcache
  + smoke container would collide).
- Add pull_request trigger running the test job only (PRs had no gate);
  skip the build job on PRs.
- Only push images from main or a v* tag (workflow_dispatch from other
  refs now builds without publishing, instead of clobbering :latest/:prod).
- Replace the log-grep smoke check with a real HTTP readiness probe
  (docker exec python3 -> http://localhost:8409/), unique container name,
  and trap-based cleanup to avoid leaks on cancel.
- dotnet test now runs -c Release --no-build (was rebuilding in Debug).
- Directory.Build.props: WarningsAsErrors=NU1904 so critical NuGet
  advisories block in every project, not just ones with
  TreatWarningsAsErrors.
- Dockerfile copies global.json + .editorconfig too, so the image build
  matches CI's SDK pin and analyzer severities.
- Remove dead .github/dependabot.yml + FUNDING.yml (upstream-pointed).
- Rewrite docs/ci-cd.md to the implemented pipeline.

Refs #4, #3, #8.
2026-06-27 00:01:22 +02:00
timothyandClaude Opus 4.6 e58bb9af21 Move CI/CD reference docs from memory to in-repo docs/
Build / Calculate version information (push) Successful in 12s
Build / build_and_upload (push) Failing after 0s
Build / build_images (push) Failing after 0s
Close stale issues / stale (push) Successful in 3s
Moves ci-cd.md (59 lines) from Claude memory into docs/ alongside
existing architecture docs. Slims MEMORY.md from 42 to 18 lines by
removing sections duplicated in CLAUDE.md (Tech Stack, Key Patterns,
Architecture Docs index).

Total memory load per session: 101 → 18 lines.

Fixes #7

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:03:31 +01:00