ffece01c320fbe88de2fdcc16740c48b7e0a3093
2809
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ffece01c32 |
style: #265 — normalize whitespace in the 11 touched files (format gate #311)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m29s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 2m56s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m21s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The fix-as-you-touch format gate requires every .cs this PR touches to fully conform to .editorconfig, including pre-existing whitespace on lines the change didn't edit. dotnet format (whitespace) applied to the 11 touched files; legacy files left untouched (no big-bang reformat). Whitespace/layout only — no behavior change. Refs #265 |
||
|
|
50cd29d841 |
fix(api): #265 review — quote-aware If-Match scanner, RFC OWS trim, de-BOM
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Failing after 2m30s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 4m23s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m44s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Independent review fix commit (cold fork MERGEABLE-WITH-NITS + Codex BLOCKED, 2 Highs):
- Codex H1: a comma (0x2C) is a valid etagc and can appear INSIDE a quoted opaque-tag
("3,5" is ONE tag). The old Split(',') broke it into two malformed tokens → 400. Replaced
with a quote-aware position scanner that treats a comma as a separator only outside the
quotes; "3,5" is now one valid non-canonical tag → 412.
- Codex H2: RFC 7230 OWS is SP/HTAB only. string.Trim() also strips NBSP and other Unicode
whitespace, letting " * " masquerade as the "*" force-write escape. Trim only
(' ', '\t'); such input is now Malformed → 400.
- Fork nit: corrected the canonical-guard comment (interior-whitespace tags are rejected by
IsEtagc, not NumberStyles.None).
- CI Formatting gate: de-BOM the 8 touched legacy Application .cs (charset=utf-8, #311/#310).
- Tests: added comma-in-tag ("3,5", "x,y","3"), empty-element tolerance, NBSP-not-OWS,
trailing-junk, lowercase-weak, wildcard-in-list cases. Full ErsatzTV.Tests green (1556).
Refs #253 #197
|
||
|
|
8090e10408 |
fix(api): #265 — If-Match evaluates per RFC 7232 (valid-but-non-matching → 412, not 400)
The shared optimistic-concurrency parser (ConcurrencyHeaders.ParseIfMatch) classified any non-canonical/weak/list If-Match value as Malformed → 400. Per RFC 7232 §3.1 a syntactically -valid entity-tag that simply doesn't strong-match must be 412; 400 is only for a genuine grammar violation. - Rewrite ParseIfMatch as a real RFC 7232 entity-tag/list parser: walks the comma-separated 1#entity-tag list, validates each [W/]DQUOTE *etagc DQUOTE member, and collects the strong members whose opaque text is our canonical decimal. Weak / empty / non-canonical / out-of-range tags are valid but contribute no version (→ empty set → 412); genuine grammar violations (unquoted, SP-in-tag, unterminated, garbage) → 400. - Reshape IfMatchCondition.ExpectedVersion : Option<int> → ExpectedVersions : Option<Seq<int>> and VersionedAggregateExtensions.CheckVersion → set membership (any strong match proceeds; empty set always 412). Threads through 10 replace/update commands + handlers + request mappers + 9 controllers. - No wire-contract change (400 + 412 already declared on every PUT; the field is header-derived and internal — no DTO/route/response-type/OpenAPI change). - Tests: ConcurrencyHeadersTests rewritten for the new classification (lists, weak, empty, non-canonical → Version/empty-set; grammar violations → Malformed) + new VersionedAggregateExtensionsTests for CheckVersion membership/empty-set/force-write. - Docs: api-conventions.md §7a rewritten; decisions.md entry appended. Refs #253 #197 fixes #265 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6537697fe5 |
test(#319): hash-drift guard prefers the built index.html when present
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 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m7s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) 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 / EF migration integrity (SQLite + MySql) (push) Successful in 4m33s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m19s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m14s
Addresses the cold-review LOW: the guard hashed only the committed source web/index.html, so a hypothetical future Vite transform of the inline script would leave the test green while the deployed CSP silently broke the SPA. It now hashes the built wwwroot/app/index.html when it exists (the exact bytes the browser hashes — full coverage on any local build), falling back to the source only in a fresh CI checkout where the built artifact is gitignored/absent. Vite copies the inline script verbatim today, so the two agree. Doc comments + decisions.md synced. Test-and-docs only; no production code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
873b3e54a5 |
security(#319): enforcing CSP + Permissions-Policy on the host
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
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 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m48s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 3m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m16s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Completes the CSP the #279 baseline-headers middleware deferred. Surfaced by the #314 ZAP baseline (missing CSP/Permissions-Policy WARNs); a #197 exit item. SecurityHeadersMiddleware now also sets Permissions-Policy (deny-all for camera/mic/geolocation/payment/usb) and an enforcing Content-Security-Policy on every response EXCEPT /docs (Scalar) and /openapi — those rely on inline bootstrap scripts/styles a strict policy would break (baseline headers still apply; hardening that admin surface is a #197 follow-up). CSP: default-src 'self'; script-src 'self' + the hash of the SPA's inline theme-bootstrap script (no 'unsafe-inline'/'unsafe-eval'); style-src adds 'unsafe-inline' (React inline styles) + fonts.googleapis.com; font-src adds fonts.gstatic.com; img-src adds data:/blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'. The Google Fonts allowance (the SPA CSS @imports the Geist web font) was caught by live-E2E, which the static recon missed. A guard test hashes the committed web/index.html inline script and fails if it drifts from the middleware constant (the built wwwroot/app is gitignored/absent in CI, and Vite copies the inline script verbatim). Verified: full test pass, live-E2E (SPA renders clean, zero CSP violations), curl (CSP present on /app + /api, absent on /docs + /openapi). Docs: docs/decisions.md entry. fixes #319 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fa655053ba |
Merge PR #316: #295 PR2 SPA session cutover + #301 side-effecting-GET POST-ification
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) Failing after 1m8s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m14s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been skipped
#295 PR2: browser SPA authenticates with the session cookie (boot gate: Setup/Login), API key demoted to machine/MCP-only (machine-key management screen), logout. #301: three side-effecting troubleshoot GET/HEAD endpoints + graphics-elements refresh POST-ified so the session-mutation CSRF gate covers them. Reviewed (fork + Codex + Fable reconciliation + fork fix-commit re-review), live-E2E'd (5/5), CI green. Fixes #295 Fixes #301 |
||
|
|
be9cf5f381 |
fix(ci): build before GenerateOpenApiDocuments in update-openapi.sh
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m32s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m37s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 2m0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 2m44s
The new api-docs gate (#303 H4/H5) runs update-openapi.sh after only `dotnet restore`. The script's `dotnet build -t:GenerateOpenApiDocuments` does not compile the project (OpenApiGenerateDocumentsOnBuild=false), so in a clean tree getdocument fails with 'ErsatzTV.deps.json does not exist' (exit 129). This PR is the first to change the /api surface and thus the first to exercise the gate's regen path, exposing the latent bug. Add a full `dotnet build` before the doc-gen target so the assembly + deps.json exist. Verified: clean-tree regen now succeeds with zero diff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
105c0ec88a |
fix(web): #316 review — playback phase promotion, engine-neutral download test fixtures, 401 signal, AuthSession optional fields
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ec26e1be5b |
fix(api): #316 review — POST-ify graphics-elements refresh, LockedError→409, no-store machine-key
- GET /api/graphics-elements no longer side-effects; refresh moved to POST /api/graphics-elements/refresh (204), closing a CSRF vector on a GET. - PrepareTroubleshootingPlaybackHandler now returns a typed LockedError from both atomic lock-acquire failures; ApiResults.ToErrorResult maps it to 409 instead of falling through to 422, so a lock lost in the race between the controller's pre-check and the handler's atomic acquire still reports 409. - AuthController.MachineKey sets Cache-Control: no-store + Pragma: no-cache on the 200 response carrying the master API key. - Reworded the stale "subtitleId query parameter" endpoint description now that playback/start takes a JSON body. - Regenerated openapi/v1.json + docs/endpoint-index.md; docs/api-conventions.md updated with the LockedError pattern (§3a) and the ToErrorResult table row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
461c763dc6 |
docs: #295 PR2 + #301 — decisions entry, api-conventions §9, e2e-local browser flow
- decisions.md: new entry (SPA cookie-only cutover, boot-gate-not-route, #301 POST-ification rationale, machine-key-read + OIDC-logout residual) + TOC line. - api-conventions §9: #301 resolved (POST-ify) + 'never add a side-effecting GET' standing rule; machine-key endpoint added to the auth surface list; PR2-shipped note. - e2e-local: fix stale 'no key required' claim (fail-closed since #197) + browser setup/login boot-gate flow. (spa-conventions §5e rewrite landed with the SPA-consumers slice.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a9f1a4f6b5 |
fix(test): #301 update OpenApiErrorResponseContractTests to POST playback/start route
Slice A updated OpenApiContractHonestyTests for the troubleshoot GET->POST route change but missed the hardcoded [TestCase] rows in OpenApiErrorResponseContractTests still referencing the removed GET/HEAD /api/troubleshoot/playback.m3u8 409 — the lookup threw KeyNotFound on the regenerated spec. Point them at the new POST /api/troubleshoot/playback/start (404/409/422, all ProblemDetails-documented). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3c1e694905 |
fix(web): #295 align MachineKey wire field to server ({ apiKey }, not { key })
The server returns MachineKeyResponse(string ApiKey) -> JSON { apiKey }, but the
hand-written SPA MachineKey type declared { key } and ApiKeyScreen read result.key,
which would be undefined at runtime (blank key + empty copy). Mocked unit tests
passed against the wrong shape. Align the type, the screen, and both test mocks to
the real { apiKey } contract.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
de9fc7e8cc |
feat(web): #295 PR2 SPA consumers — troubleshoot POST/blob downloads + machine-key screen + password change
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9348a9938a |
feat(web): #295 PR2 SPA session-auth foundation (boot gate, CSRF header, login/setup/logout)
Replace the API-key SPA model with the session-cookie auth from PR1: - client.ts: stop sending X-Api-Key; attach X-Csrf on mutating verbs (POST/PUT/ PATCH/DELETE); add suppressUnauthorizedSignal to skip the global 401 signal on expected wrong-credentials 401s (login / change-password). - api/auth.ts: hand-written wire types (AuthConfig/AuthSession/MachineKey — the auth surface is IgnoreApi, deliberately not in generated types) + endpoint fns (getAuthConfig/getAuthSession/login/setup/logout/changePassword/getMachineKey); keep the notifyUnauthorized/subscribeUnauthorized 401 signal; add clearLegacyStoredApiKey. Legacy get/set/clearStoredApiKey retained ONLY so the still-shipping ApiKeyScreen (machine-key slice) compiles without a cross-slice conflict — the client no longer reads them. - AuthGate.tsx: boot gate wrapping <App/> outside the shell (mints no URL, deep links survive login). checking -> setup | login | ready | error, with a safe default AuthContext so App.test.tsx renders without a provider; config-fetch failure lands on an explicit error+Retry, never a blank screen. - LoginScreen / SetupScreen: shell-less centered cards; inline 401 / 409 handling; SSO button + local-form gating from AuthConfig. - UnauthorizedBanner: rewritten to prompt re-login (passive; consults the unsaved-changes guard before flipping the gate). - UserMenu: TopBar sign-out (guard -> logout -> signOut), mounted next to ConnectMenu. - main.tsx: wrap <AuthGate><App/></AuthGate> inside StrictMode. Tests: client/auth/AuthGate/LoginScreen/SetupScreen/UnauthorizedBanner/UserMenu (723 pass). Lint + build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
88d82266b6 |
feat(api): #301 POST-ify side-effecting troubleshoot GETs + GET /api/auth/machine-key
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9dc48118d1 |
chore: re-trigger CI (api-docs runner checkout-cache flake on 5c5a90af; identical tree)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m44s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) 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 / Build & test (.NET) (push) Successful in 8m27s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m28s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m48s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5c5a90afde |
fix(ci): #315 address review nits — drop fragile FAIL_RE, validate --timeout, log image id
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) Failing after 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 18s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m3s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 8m48s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Adversarial review (MERGEABLE-WITH-NITS) findings: - Remove the broad FAIL_RE log-scan (matched benign ErsatzTV startup noise — library scans against absent media mounts, EF connection retries — risking a false-FAIL that blocks a good release). It was also redundant: a failed migration faults the BackgroundService -> default StopHost -> container exit, which the early-exit check already catches reliably (per the reviewer's own analysis). Migration failure is now detected by early container exit + timeout + the post-boot serve probe. - Validate --timeout is a positive integer (was: '--timeout abc' -> 0 -> instant false-FAIL). - Log the resolved image id after (attempted) pull, so a pull-failure that rehearses a stale local :latest is visible to the operator. Re-validated live on bumblebee: :latest vs the 283MB prod-copy -> migrations clean, PASS, image digest logged, no leftover temp dir/container. shellcheck + bash -n clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
90c8348efe |
fix(process): #317 docs-only exemption stays passthrough, not silent auto-grant [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 / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m22s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m30s
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 4m33s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m36s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m33s
Adversarial-review nit on the first commit: auto-granting the docs-only exemption silently self-merges process-control PRs (.claude/.gitea/.husky — including the gate hook itself) with no prompt and no review, bypassing human-in-the-loop for exactly the files that control the gate. Restrict auto-grant to the genuinely- satisfied (a+b+c) merge path; the docs/process exemption reverts to bare exit-0 passthrough (one normal prompt). Corrects this PR's own decisions.md entry accordingly ([decisions-edit]: a not-yet-merged draft entry, not history). 8 pipe tests green (adds docs-only -> passthrough). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8565f731cd |
fix(process): #317 merge-consent gate auto-grants on satisfied path (no double-prompt)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m58s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 8m39s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The H6/H10 merge-consent hook's satisfied path did a bare `exit 0`, which does not auto-approve a PreToolUse tool call — it only declines to block, so control fell through to the normal permission system and the raw MCP prompt still fired. A ready-to-merge PR was therefore confirmed twice (conversationally + a redundant mechanical prompt). Emit permissionDecision "allow" (new `grant` decision) on the satisfied and docs-exempt paths so the derived state IS the consent; deny/ask unchanged (fail-closed); non-merge methods keep the exit-0 passthrough. Docs: CLAUDE.md, kickoff HARD CONSTRAINTS, docs/decisions.md (append-only, pure insert). Verified: 7 pipe tests (satisfied->allow, unticked->deny, stale->deny, red-CI->deny, no-verdict->ask, no-creds->ask, non-merge->passthrough). fixes #317 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b3b3963fe |
Merge pull request 'process: #311 H11 rebase-hook + PR-scoped format CI + #312 H12 qualification audit' (#313) from ci/311-rebase-hook-format-ci into main
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) 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 / Build & test (.NET) (push) Successful in 5m21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 8m24s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m35s
|
||
|
|
f1b2521228 |
chore(process): #312 H12 — session-end issue-qualification audit
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m31s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m9s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
scripts/issue-qualification-audit.sh: lists OPEN issues missing a `priority:` label (the #237 ranking keys off priority:/gate labels, so an unlabeled issue is invisible to it). "Fully qualified" = has a priority: label; gate-vs-backlog derives from the review label / milestone, and a milestone is NOT required (backlog is unmilestoned). Advisory (exit 1 if any unqualified); fail-open without Gitea creds. Wired into the kickoff session-end protocol + a lore bullet. Tested live (flagged 2) + no-creds no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
19dbfe9a8b |
docs(process): #311 — formatting-as-you-touch (§7) + rebase-not-merge lore + decisions entry
- contributing.md §7: document fix-formatting-as-you-touch (normalize a legacy file you edit, incl. BOM strip; no big-bang), enforced by the pre-commit hook + the new format CI job. - kickoff lore: flip "merge main into PR branch" -> "rebase on origin/main" (H11 enforces it); keep the regenerate-generated-artifacts-on-conflict nugget. - decisions.md: new entry (+ TOC) for both decisions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3d07c6818d |
ci(process): #311 H11 rebase-before-push hook + PR-scoped format-verify CI job
- H11: .husky/pre-push calls .claude/hooks/prepush-rebase-check.sh, which blocks a push whose branch is behind origin/main (rebase first; do not merge main in — a merge drags in files you didn't touch, e.g. legacy-BOM .cs, tripping the format hook on code that isn't yours). Fail-open; escape ETV_SKIP_REBASE_CHECK=1. - New blocking `format` CI job: dotnet format --verify-no-changes scoped to the PR's changed .cs only (style + charset=utf-8/no-BOM), enforcing fix-as-you-touch without a big-bang reformat of the ~2500 legacy BOM files. .cs-free PRs skip and pass (always reports a status). Closes the "CI never checks charset" gap that let #269 land 17 BOM files (#310). Docs (contributing.md §7 / decisions.md / lore) follow in the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf4cb5117e |
Merge pull request 'docs(process): #303 follow-ups — Codex-skip rubric + write-path live-E2E requirement' (#309) from ci/303-process-followups into main
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 / Build & test (.NET) (push) Successful in 8m16s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m37s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m9s
|
||
|
|
b20ee50b1f |
docs(process): #303 follow-ups — Codex-skip rubric + write-path live-E2E requirement
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 / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Two of the four non-hook #303 process follow-ups (the two docs items; the security scan and migration-on-prod-copy smoke are deferred to their own sessions): 1. Codex-skip rubric (kickoff workflow lore): an independent review pass is MANDATORY for diffs touching locks/concurrency, auth/security, API write-path handlers, or migrations, or >~150 changed C# lines; skippable only for a pure-SPA/docs leaf, and a skip must be stated + justified. Makes self-exemption an auditable claim (the correlated-blindspot net). 2. Live-E2E is now a STATED REQUIREMENT for API write-path handler changes: new "When live-E2E is required" section in docs/e2e-local.md + a decisions.md entry, formalizing the #229 lore bullet. The seeding recipe was already in e2e-local.md (added for #220), so the stale "recipe not yet in docs" lore bullet is pruned to a pointer. Docs-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7a9b30de71 |
fix(api): #269 review fixes — rebase force-write delta so rotation survives a race (Codex F1/F3)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 6s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m20s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m50s
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 / EF migration integrity (SQLite + MySql) (push) Successful in 3m43s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 6m51s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m26s
Independent Codex review (reconciled by Fable against a MERGEABLE fork verdict) found SaveChangesForcingVersion silently DROPPED a pending Version++ under a concurrent versioned-write race: on DbUpdateConcurrencyException it adopted the DB's current Version verbatim (original = current = dbVersion), so a bumping sibling committed at dbVersion instead of dbVersion+1. Net: an editor holding the concurrent writer's ETag was never invalidated by the sibling's change — the exact lost-update the #253/#269 contract exists to close, lost under the very condition the helper handles. F1 fix (shared helper, corrects all 25 bumpers incl. the pre-existing Add*ToPlaylist / schedule-item writers): rebase the pending delta on top of the stored token — pendingDelta = current - original; original = dbVersion; current = dbVersion + pendingDelta Bumpers (delta 1) advance to dbVersion+1; non-bumpers/deletes (delta 0, e.g. ErasePlayoutHistory) still adopt the stored token unchanged, so RootWriterForceVersionTests is unaffected. Idempotent across the bounded retry loop. F3: the force-race tests now assert Version==3 (rebase), not just membership survival; added the missing Playout force-race+rotate test. Negative-controlled: with the helper fix reverted, both strengthened tests go red. F2 (Medium, deferred → #308): two concurrent same-item Add*ToCollection can both pass the membership check and the loser 500s on the composite-PK violation (DbUpdateException, which the helper doesn't catch). Pre-existing and narrow (no corruption); doc claims softened to name it. Filed #308. Docs: api-conventions §7a + decisions.md prose corrected from "adopt the stored token" to the rebase semantics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
83f753b211 |
fix(api): #269 rotate aggregate ETag on Collection/Playout config siblings
Complete the #253 optimistic-concurrency contract's cross-editor ETag rotation tail. The non-If-Match config siblings mutated editor-visible state without bumping Version, so a concurrent editor of the same root never invalidated. Now the Collection Add*/Remove handlers bump Collection.Version, and UpdateCollection / UpdatePlayout / the three ScheduleFile writers (which already force-wrote past a concurrent bump) now bump too — all via SaveChangesForcingVersion (no If-Match → force write, never 412/500). No-op idempotence (Fable-caught trap): these gate reindex/BuildPlayout fan-out on SaveChanges()>0, so an unconditional bump would fire spurious rebuilds on an idempotent re-add / same-value re-submit. Each now short-circuits a genuine no-op before the bump — Add handlers by an explicit membership check (also fixing a latent duplicate-CollectionItem insert), scalar writers by ChangeTracker.HasChanges(). Corrects #269's framing: the Add*ToCollection family is not repository-mediated (IMediaCollectionRepository is read-only); each handler writes via its own dbContext, so the scanner's separate membership path is unaffected (a background scan does not rotate the editor ETag). Tests: CollectionEtagRotationTests + PlayoutScheduleFileEtagRotationTests (rotation, no-op-without-bump-or-rebuild, force-write-past-concurrent-bump), no-op guard proven non-vacuous by inverting the membership check. Docs: api-conventions §7a + decisions.md. No new status codes / no OpenAPI change (these endpoints take no If-Match, never 412). The #265 RFC-7232 If-Match parser refinement is a separate PR. fixes #269 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2879c012ba |
Merge pull request 'chore(process): #303 H10 — review-verdict merge-gate (latest commit must be reviewed)' (#306) from ci/303-h10-review-verdict-gate into main
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 / Build & test (.NET) (push) Successful in 8m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m54s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m16s
|
||
|
|
938733c3d0 |
fix(process): #303 H10 — anchor is_pos to the leading marker (monotonic hardening)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
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 8m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Final re-review returned SHIP-IT with one contrived, pre-existing residual: a line starting `Review-verdict: BLOCKED …` that ALSO contains a second literal `review-verdict: mergeable` substring later on the same line read as positive. Anchor the is_pos check to line-start so only the line's OWN leading verdict word counts. Safe-by-construction: anchoring a positive matcher can only REDUCE the allow-surface, so it cannot introduce a false-open (the dangerous direction); the 21-case regression confirms no false-deny (all still allow/deny/ask as before) + a new B6 case for this exact vector. No further review round needed — the change is monotonic. shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ff3df39c43 |
fix(process): #303 H10 — anchor verdict marker to line-start (close self-reference false-open) [decisions-edit]
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 12s
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 4m59s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m13s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Second adversarial re-review (of the fix commit itself — dogfooding H10) found
a remaining false-open: the `review-verdict:` marker was matched anywhere on a
line, so a comment merely QUOTING the positive template — an instruction
("please post: Review-verdict: MERGEABLE @ <head>"), a blocking comment
explaining how to clear itself, or the gate's OWN suggestion text echoed into a
comment — was classified as a real head verdict and self-approved the merge.
Fix: anchor the marker to line-start (`^[[:space:]]*review-verdict:`). A real
verdict line starts with the marker; quoted/instructional uses have text before
it. Also drops the dead `nosha` var (SC2034).
Finding 2 (a BLOCKED mis-anchored to an OLDER sha doesn't retract a
MERGEABLE@head) is deliberately NOT "fixed": staleness is symmetric — a
pre-fix BLOCKED@oldsha must not block forever after the fix changes the sha and
earns a fresh MERGEABLE@head. To retract, re-review head and post BLOCKED@head.
Documented in decisions.md.
Pipe-tested 21 cases (5 new: instructional-quote, self-reference reason-text,
line-start-in-multiline, leading-whitespace, blocking-quotes-template), all
deny/ask/allow correct. bash -n + shellcheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f87a2092c4 |
fix(process): #303 H10 — anchor sha match to the @<sha> field; retraction-wins [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
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 5m13s
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 found false-opens in the first cut:
- `grep -F "$short"` was an unanchored substring test: a MERGEABLE verdict for
a DIFFERENT/older commit was accepted whenever the head 7-prefix appeared
anywhere on the line (inside a longer sha, or an unrelated commit URL). Now
each verdict line's `@ <sha>` token is extracted and matched to head by git
short-sha prefix semantics (head begins with token, token >=7 chars).
- No retraction semantics: a later `BLOCKED @ head` didn't override an earlier
`MERGEABLE @ head`. Now a negative verdict on head wins -> deny.
- A 7-digit build number falsely tripped the "references an older commit" deny;
the `@`-anchored parse fixes it -> a marker with no @sha now asks, not denies.
Also documents the issue-comment scope (gate reads issues/{pr}/comments, not
Gitea formal-review bodies). Pipe-tested: 16 cases incl. 4 adversarial
false-open reproductions, all now deny/ask. bash -n + shellcheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9fd8f40541 |
chore(process): #303 H10 — review-verdict merge-gate (latest commit must be reviewed)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m43s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Folds condition (c) into pretooluse-merge-consent.sh (H6): a PR merge is allowed only when a `Review-verdict:` comment references the PR's CURRENT head sha — proving the latest commit was reviewed, not a stale earlier diff (mechanizes the ersatztv#242 "re-review the fix commit" lesson). Graceful adoption mirrors H6's Done-when tiering: - positive verdict @ head -> allow - verdict @ older sha (stale) -> deny (#242 failure mode) - head verdict negative -> deny - marker with no sha / none yet -> ask - comments unfetchable -> ask Reuses H6's PR fetch, docs-only exemption, and Gitea-auth-from-env (one hook, no detection drift — per the #303 methodology review). Pipe-tested 12 cases. Docs: decisions.md (new H10 entry + TOC), CLAUDE.md Task Completion Protocol. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0123e58914 |
Merge pull request 'chore(process): #303 Wave 3 — H3 root-screenshot guard + H9 decisions.md append-only guard' (#305) from ci/303-wave3-guards 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 / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m23s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m24s
Merge pull request '#303 Wave 3 — H3 root-screenshot guard + H9 decisions.md append-only guard' (#305) from ci/303-wave3-guards into main Part of #303. Adds the Husky/CI append-only guard for docs/decisions.md (with the [decisions-edit] escape, mark-and-keep supersession, and a 1800-line read-cost consolidation floor) and the root-screenshot pre-commit guard. Reviewed MERGEABLE; user-approved merge. |
||
|
|
9a6c98f629 |
ci: re-trigger — MySql migration-integrity flake on prior run (no code change)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 15s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 16s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Run 553 hit the known EF-migration MySql-apply flake (concurrent-runner contention; MEMORY ci-migration-job-mysql-flake). This PR touches only hooks/docs/yaml — the identical pipeline passed fully green on the parent commit (run 552). Empty commit to get a clean run; Gitea 1.24 has no job-rerun API endpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
56ef1df936 |
feat(process): #303 H3 root-screenshot guard + H9 decisions.md append-only guard
H3 (Husky pre-commit): reject a staged root-level *.png — belt-and-suspenders with the .gitignore screenshot rule so `git add -f` still can't land a review artifact. H9 (append-only decisions.md): new shared hook `.claude/hooks/decisions-guard.sh`, wired into Husky commit-msg (staged mode) and a new blocking `decisions-guard` CI job (range mode). Blocks any commit/PR that deletes or modifies an existing line of docs/decisions.md — detected via `git diff --numstat` deleted-count, robust to markdown `-` list markers — unless the message carries the `[decisions-edit]` token. Pure insertions (a normal new entry) always pass. One implementation for local + CI so they can't drift. Fail-open on any tooling trouble. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a81f024840 |
Merge remote-tracking branch 'origin/main' into ci/303-api-docs-blocking
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m21s
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 / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m19s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m30s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m30s
|
||
|
|
08d633c687 |
Merge pull request 'fix(api): #269 force-write non-If-Match root writers past a concurrent Version bump' (#302) from fix/269-force-version-on-root-writers into main
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 3m53s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m57s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
|
||
|
|
c34d2bdbf2 |
Merge remote-tracking branch 'origin/main' into ci/303-api-docs-blocking
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 19s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m7s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
# Conflicts: # docs/decisions.md |
||
|
|
0badff811d |
feat(process): #303 H6 merge-consent derived from ## Done-when checklist
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m26s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m32s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m48s
Wave 2 hook H6: derive merge-consent from state instead of memory. An issue's ## Done-when checklist (issue body) becomes the machine-readable source of truth for whether its PR may merge — the structural fix for the queue-drift #303 tracks (status was living in append-only prose). - pretooluse-merge-consent.sh (Claude PreToolUse on mcp__gitea__ pull_request_write): a merge is ALLOWED only when the PR's CI is green AND every ## Done-when box on the linked issue (fixes #N) is ticked; DENY on an unticked box / red CI; ASK (human prompt) when state isn't derivable (no linked issue, no section, no creds, Gitea down). Docs/ process-only PRs exempt. - .husky/pre-push -> prepush-donewhen.sh: fail-open backstop for a direct `git push origin main`; blocks only on a positively-proven unticked box. Gitea auth from env only (ETV_GITEA_BASICAUTH / ETV_GITEA_TOKEN, ETV_GITEA_URL) — nothing committed; without creds the gate degrades to today's manual confirmation, never a silent pass. Non-breaking rollout: until issues adopt ## Done-when the merge hook simply asks. Pipe-tested: non-merge->allow, no-creds->ask, docs-only->allow, checklist parser (unit), linked-issue extraction, and a live end-to-end block path (temp Done-when on #303 -> exit 1 -> restored). Docs: CLAUDE.md Task Completion Protocol + decisions.md entry. Refs #303. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aa2e13fa51 |
ci: #303 H4/H5 blocking api-docs gate — fail on stale OpenAPI artifacts
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m15s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Adds a blocking `api-docs` CI job: when a PR diff touches the API surface (ErsatzTV/Controllers/Api/** or ErsatzTV.Core/Api/**) it rebuilds the generated artifacts from source — v1.json, v1.d.ts, endpoint-index.md — and fails if any is stale in the diff. Mechanizes the "docs-update in the same PR" rule for the API contract (docs-reminder stays a non-blocking route-parity nudge). Path-gated INSIDE the job (per-step `if:` on a detect output), not via a top-level `if:`, so the check always reports a status on every PR and is safe as a required check: API-free PRs skip the dotnet/node setup + regen and pass trivially. Verified the gate reproduces the committed baseline: a fresh build regenerates v1.json byte-identical to HEAD (incl. all 244 auth security/401 blocks). The only footgun is local — update-openapi.sh runs dotnet-getdocument against the already-built assembly, so a stale bin/ emits a stale spec; api-conventions.md §5 now flags "build first". CI is immune (fresh checkout has no bin/). Docs: api-conventions.md §5 (two-place CI enforcement + stale-assembly note), decisions.md (new entry). Refs #303. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5a12aae66e |
Merge remote-tracking branch 'origin/main' into fix/269-force-version-on-root-writers
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m22s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m11s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
# Conflicts: # docs/decisions.md |
||
|
|
171d30c709 |
Merge remote-tracking branch 'origin/main' into fix/269-force-version-on-root-writers
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m58s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
# Conflicts: # .gitignore |
||
|
|
21b49e6a42 |
chore(#269): remove accidental web/node_modules symlink from PR
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m27s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m37s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Fix-commit re-review (cold fork) caught that
|
||
|
|
48d256f83b |
Merge origin/main into feat/295-auth-pr1
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m27s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m36s
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 6m7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m11s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m36s
Brings in the #303 process/rigor hooks + docs. Only conflict-free overlap was docs/decisions.md (main added the ## Index TOC); reconciled by adding the #295 auth entry to the index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
35e41fdaad |
chore(process): #303 H7 worktree-owner guard + complete Wave 1 wiring
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 10m13s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Wave 2 hook H7: never commit/merge inside a sibling worktree another session created (burned us on #289 path-leak + the plumbing-merge workaround). Ownership = a per-session .claude-worktree-owner marker: - posttooluse-worktree-marker.sh stamps a worktree with session_id on `git worktree add` (parses the <path> arg past -b/-B/--reason flags). - pretooluse-worktree-guard.sh denies `git commit`/`git merge` whose effective dir (resolves `git -C <p>` and leading `cd <p> &&`) is a worktree whose marker names a DIFFERENT session. Fail-open: no marker, unparsable, or own session -> allow. Main tree + pre-convention worktrees are never marked, so unaffected. Also completes Wave 1's rollout, which committed pretooluse-bash-guard.sh but left .claude/settings.json and the agent-ram/nav-guard hooks untracked (so nothing was actually wired). Adds the settings.json that registers all five hooks (PreToolUse Bash x2, nav, Agent; PostToolUse Bash) + the .gitignore worktree-marker line, screenshot-scratch rules, and the decisions.md TOC left uncommitted last session. All hooks pipe-tested (7 guard cases + 6 marker cases). Refs #303. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e383c253cc |
fix(api): #269 review — force-write ErasePlayoutHistory + document boundaries
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m54s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m45s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Independent adversarial review (cold fork + Codex) of the first cut converged
on one real miss and two boundaries to document.
- **ErasePlayoutHistoryHandler** (HIGH, both reviewers): modifies Playout ROOT
scalars (Seed/Anchor/OnDemandCheckpoint) *without* bumping Version, inside an
explicit transaction with no try/catch, so it 500s on a concurrent bump —
reachable via POST /api/playouts/{id}/erase-items-and-history. My first sweep
filtered on "Version-bumpers + deletes"; the true exposure surface is "any
handler leaving a versioned root Modified/Deleted", so this slipped through.
Now routes through SaveChangesForcingVersion (+ a non-vacuous through-handler
test that exercises the explicit-transaction path). Re-swept with the correct
filter: ErasePlayoutItems (AsNoTracking + ExecuteDelete children only) and
ResetAllPlayouts (read-only + enqueue) are NOT exposed.
- **Background build/time-shift Playout-scalar writers** (BuildPlayout via
PlayoutBuilder, PlayoutTimeShifter): token-guarded too, but intentionally left
on plain save — they already catch (build-failure, not 500), and force-writing
would persist output built from stale config (the concurrent config bump already
enqueues a rebuild). Documented as a deliberate boundary, not a gap.
- **Item-add index collision** under force-write: documented as an accepted
Phase-1 effect (non-corrupting, self-correcting; reload-recompute refinement
is a #197 candidate).
Also corrects the docs' "every Version bumper" framing to the true filter and the
test docstring's over-broad non-vacuity claim. Full ErsatzTV.Tests green (1483).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|