6b41340abe36f00a6c9d353ed1465d7060a9daed
3581
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6b41340abe |
chore(deps): update dependency sqlitepclraw.bundle_e_sqlite3 to 3.0.5
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / Docs update reminder (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 34s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m21s
review-verdict/h10 Exempt: authored by the 'renovate' bot account, touches no protected path, and changes only dependency manifests
Review verdict / Set review-verdict status (pull_request_target) Successful in 34s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m18s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m59s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m35s
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 5s
|
||
|
|
9928be805f |
Merge pull request 'chore(deps): batch three Renovate patch bumps (supersedes #679, #680, #681)' (#714) from chore/renovate-batch-2026-07-30 into main
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 & test (.NET) (push) Successful in 16s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 27s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 28s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 10m54s
Build CI Toolchain Image / Build & push CI image (push) Successful in 9m5s
Dependency vulnerability scan / NuGet vulnerable packages (push) Successful in 1m10s
Renovate / Renovate (push) Successful in 2m20s
|
||
|
|
fbbdaeca3c |
chore(deps): batch three Renovate patch bumps
PR Gates / Docs update reminder (pull_request) Successful in 22s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 25s
PR Gates / decisions lifecycle (pull_request) Successful in 32s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 58s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 57s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m8s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m23s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 20m9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
review-verdict/h10 Review-verdict: MERGEABLE @ fbbdaec (base: main)
Review verdict / Set review-verdict status (pull_request_target) Successful in 5s
Consolidates the three open Renovate PRs into one change so they land on a single CI pipeline instead of three. They all edit Directory.Packages.props, so merging them individually would force a rebase + full re-run for each remaining PR. CliWrap 3.10.2 -> 3.10.3 Meziantou.Analyzer 3.0.115 -> 3.0.129 SQLitePCLRaw.bundle_e_sqlite3 3.0.3 -> 3.0.4 Also refreshes the #8 security-pin comment, which named 3.0.3 explicitly. Renovate only rewrites the version attribute, so its own PR would have left that comment contradicting the line directly beneath it. The pin's intent is unchanged: stay on the 3.x line that ships the patched native SQLite (GHSA-2m69-gcr7-jv3q), and 3.0.4 still satisfies Microsoft.Data.Sqlite's `>= 2.1.10`. All three original PRs went red on 2026-07-27, but none of the failures reached any code. The runner host had exhausted its disk at ~03:20 UTC: tar cache-restore failing with "No space left on device", SQLite Error 13 "database or disk is full", and ErsatzTV refusing to boot for want of 128 MB of free space. #681's migration job in particular died during cache restore, before either the SQLite or MySQL half ran, so the native-bundle bump was never actually exercised there. Verified locally on this combined change: - dotnet build -c Release: 0 errors; zero MA/S/CA analyzer diagnostics, so the Meziantou 3.0.115 -> 3.0.129 jump introduces no new rules that bite - full test suite: 4440 passed, 0 failed across all 7 test projects - SQLite model drift clean + all migrations applied to a fresh DB, which is the exact job that was red on #681 Supersedes #679, #680, #681. |
||
|
|
f9cbd152bc |
Merge pull request 'chore: ignore .codex/, and stop shipping a plaintext credential in docs' (#712) from chore/codex-ignore-and-credential-redaction into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 16m45s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 20m5s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 22m4s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m18s
|
||
|
|
980da6db00 |
chore: ignore .codex/, and stop shipping a plaintext credential in docs
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 10s
PR Gates / Docs update reminder (pull_request) Successful in 16s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
Review verdict / Set review-verdict status (pull_request_target) Successful in 21s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m15s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 20m3s
review-verdict/h10 Review-verdict: MERGEABLE @ 980da6d (base: main)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m44s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
.codex/ is generated by `codex exec` as a machine-local mirror of the .claude hooks. It is deliberately NOT tracked even though .claude/ is (17 files): its config.toml embeds a plaintext Gitea credential and absolute /Users paths, so committing it would leak the credential and would not be portable anyway. Ignoring it also unblocks scripts/refresh-shared-checkout.sh, which refuses on a dirty tree. Separately, docs/handoffs/rest-api.md carried the same credential inline; it now references $ETV_GITEA_BASICAUTH like every other doc. NOTE this does not purge git history — the literal appears in 12 earlier commits and is still recoverable there. Refs: #698 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
81be685df9 |
Merge pull request 'docs(698): correct the Renovate auto-pass rule in CLAUDE.md' (#710) from docs/698-claudemd-renovate-rule into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 38s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 38s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 39s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 10s
|
||
|
|
1eca9b0c11 |
docs(698): correct the Renovate auto-pass rule in CLAUDE.md
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 18s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 39s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 38s
review-verdict/h10 Exempt: docs-only change (no code, no protected path)
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 38s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 38s
Review verdict / Set review-verdict status (pull_request_target) Successful in 24s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
PR Gates / Script tests (pytest) (pull_request) Successful in 1m0s
CLAUDE.md still described the bot exemption as identity-only (auto-passed unless a protected path is touched). Since #698 it also requires EVERY changed path to be a dependency manifest — a bot account does not attribute the code at a head. CLAUDE.md is loaded every session, so a stale rule here is worse than a stale doc. Refs: #698 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a3458e6e2c |
Merge pull request 'fix(698): bind the base, constrain the bot exemption by content, re-derive unattributable successes' (#705) from fix/698-exemption-provenance into main
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 & test (.NET) (push) Successful in 37s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 37s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 38s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 16m20s
|
||
|
|
57e33f9937 |
chore(698): drop a trailing blank line at EOF
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
PR Gates / Docs update reminder (pull_request) Successful in 18s
PR Gates / decisions lifecycle (pull_request) Successful in 24s
Review verdict / Set review-verdict status (pull_request_target) Successful in 12s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m31s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m3s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
review-verdict/h10 Review-verdict: MERGEABLE @ 57e33f9 (base: main)
Nit from review round 6 (git diff --check). No behaviour change. Refs: #698 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fe00e0d71f |
fix(698): compare the recorded base exactly, never parse it out
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 37s
PR Gates / Docs update reminder (pull_request) Successful in 40s
PR Gates / decisions lifecycle (pull_request) Successful in 45s
review-verdict/h10 Awaiting review verdict for fe00e0d
Review verdict / Set review-verdict status (pull_request_target) Successful in 14s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m23s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m19s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m34s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m12s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 21m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 24m7s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Review round 5 returned BLOCKED with one High, and it needed no forgery and no #697 — just a branch name. `main)evil` IS A VALID GIT BRANCH NAME (`git check-ref-format --branch 'main)evil'` succeeds). A genuine human verdict earned while head H targeted it is written `(base: main)evil)`. Truncating at the first `)` yields exactly `main`, which matches a PR that has since been retargeted onto `main`, so the verdict is inherited over a completely different diff. I had asserted the opposite in a code comment one commit earlier — that a `)` in a branch name "mismatches — safe direction". That was generalised from `feat/foo)bar`, which does mismatch, and is false for EVERY branch whose name starts with the target base. Two attempts at extracting this value have now been defeated (`##` last-marker by an appended marker, `#` first-marker by this), so the lesson is the shape, not the off-by-one: do not parse a value out of user- or attacker-influenced text when you can compare against the exact expected literal instead. The description must now END with the literal `(base: <this PR's base>)` AND contain exactly ONE marker — the marker count kills the append trick without having to decide which occurrence is authoritative. Pure shell (`${#}` arithmetic), no truncation to abuse. Verified across all six shapes, including a PR that legitimately targets `main)evil` (accepted) and `(base: )` (rejected). Absent markers remain accepted, since verdicts predating #632 carry none. Mutation-verified: restoring the truncating parse reddens only the new paren test, while the appended-marker, matching-base and legacy tests stay green. 385 tests pass. Note for the record: pytest has never executed inside the review sandbox in any of the five rounds, so the suite has only ever been run here. Refs: #698 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ef92b46dd2 |
fix(698): parse the recorded base at its FIRST occurrence, not its last
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 17s
PR Gates / Docs update reminder (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 28s
review-verdict/h10 Awaiting review verdict for ef92b46
Review verdict / Set review-verdict status (pull_request_target) Successful in 23s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m31s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m31s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m40s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m35s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m29s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Self-found while writing the round-5 review brief, by asking what an attacker who can influence the status description (#697) could do to the parse I had just added. `${ex_desc##*"(base: "}` is greedy, so it reads the LAST occurrence. A description of `Review-verdict: MERGEABLE @ abc1234 (base: probe/scratch) (base: main)` therefore parsed as `main`, matched the PR's base, and the verdict was inherited — reopening the exact hole the base check was added to close, one commit earlier. Measured both forms before choosing: first-match yields `probe/scratch`, mismatches, and fails closed. Two adjacent cases confirmed to fail in the safe direction: a `)` inside a branch name truncates the value (mismatch), and an empty `(base: )` is present-but-different (mismatch), so neither is waved through by the legacy-absent-base allowance. Tests for both, and the appended-base test is mutation-verified: restoring `##` reddens it. 384 tests pass. Refs: #698 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e7bae06385 |
fix(698): review round 5 — a human verdict formed against ANOTHER base is no longer inherited
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 13s
PR Gates / Docs update reminder (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 23s
review-verdict/h10 Awaiting review verdict for e7bae06
PR Gates / decisions lifecycle (pull_request) Successful in 31s
Review verdict / Set review-verdict status (pull_request_target) Successful in 21s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m5s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m27s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m23s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 20m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m13s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Round-4 cross-family review returned BLOCKED with a single Medium; the three round-3 items were confirmed actually fixed. THE SHA-BINDING WAS ESCAPABLE THROUGH THE HUMAN PATH, not the exemption path. The short-circuit identified a human verdict by creator + `Review-verdict:` prefix and then exited before looking at the base. So: earn a GENUINE `success` on head H while it targets a scratch base with a benign diff, then retarget H onto `main`, where its diff carries unreviewed code. Creator real, prefix real, status inherited — a green required check over code nobody reviewed. `post-review-verdict.sh` has recorded the reviewed base in the description since #632; this gate simply never read it. The merge-consent hook did compare it, but that is advisory and covers only its own path: a merge through the Gitea UI or API sees nothing but the status. The gate now rejects a verdict whose recorded base differs from the PR's. An ABSENT base is deliberately NOT a mismatch — verdicts predating #632 carry none, and re-deriving over one would un-approve a genuinely reviewed head. Only present-and-different is rejected, which is exactly the escape. Tests: the mismatch case, plus two positive controls (matching base still short-circuits; a legacy no-base verdict still short-circuits) so the check cannot pass by blanket rejection. Mutation-verified: removing the check reddens only the mismatch test. Also from round 4: sharpened the docstring of test_the_classify_step_runs_without_SHELL_ERRORS. It catches guards that die NOISILY; it is not a general liveness check, since a clean mutation like hardcoding n_protected=0 emits nothing. The branch-discriminator test is the actual liveness guard. Claiming otherwise would have made a cheap net look like a strong one. And fixed a dangling decision key I had just introduced: the base-in-description convention belongs to `release.verdict-status-check`, not the `ci.verdict-records-base` I invented — the breadcrumb hazard our own retrieval rules warn about. 382 tests pass. Refs: #698 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d4c600149d |
fix(698): review round 4 — the PROTECTED guard was DEAD; define before use, fail closed, fix prescriptive docs
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
review-verdict/h10 Awaiting review verdict for d4c6001
Review verdict / Set review-verdict status (pull_request_target) Successful in 13s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 22s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m38s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m30s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Round-3 cross-family review returned BLOCKED with 3 Mediums. The first was serious
and self-inflicted.
THE PROTECTED GUARD WAS A NO-OP. Round 3's `count_matching` / `count_not_matching`
helpers were defined AFTER the classification chain that calls them, so
`count_matching` was `command not found` on every run, `$( )` yielded an empty string,
`[ "" -gt 0 ]` errored, and the `elif` was simply skipped — the protected-path check
never executed at all. Confirmed by direct execution before fixing.
Three "protected path" tests stayed GREEN throughout, because a protected path is also
not a manifest and not docs-only, so the job still reached `pending` down a different
route. Asserting the STATE could not distinguish a working guard from a dead one. The
mutation battery missed it too: I had mutated the predicates, not their reachability.
Fixed three ways:
* helpers are defined immediately after `gh()`, before any use;
* the three counts are evaluated ONCE at TOP LEVEL and validated numeric, because
`exit 1` inside `$( )` leaves only the subshell and, with the substitution sitting
in a conditional, `set -e` never fires either — so a grep error had been silently
reading as "no match". A non-numeric result now aborts with nothing posted, and an
absent required check blocks the merge;
* the helpers return a non-numeric sentinel instead of trying to `exit`.
Verified: an invalid regex now exits 2 and posts NOTHING (previously it classified and
posted). Renaming the helper at its definition turns six tests red.
TESTS, aimed at the failure mode rather than the symptom:
* assert the DISCRIMINATOR (the job's `Decision:` reason line), not the outcome —
when several branches yield the same verdict, the verdict cannot tell you which ran.
A first draft of this test asserted the status description and failed against a
WORKING guard, because for `pending` the description is constant;
* a cheap stderr sweep for `command not found` / `integer expression expected` /
`unbound variable` across four representative PR shapes. Each of those makes an `if`
condition merely false while the job exits 0 and posts a plausible status, so this
catches a whole family of silently-skipped guards.
DOCS. The record and ci-cd.md still PRESCRIBED the here-string that round 3 removed —
following them would have reintroduced the temp-storage failure. Both now prescribe
counting, define-before-use, top-level evaluation and numeric validation. The workflow's
measurement paragraph still said the npm manifests "are included" three lines above the
note saying they are excluded; corrected.
379 tests pass.
Refs: #698
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d8bd1dcba9 |
fix(698): review round 3 — count instead of matching, re-read before the POST, fix stale docs
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 18s
PR Gates / Docs update reminder (pull_request) Successful in 23s
PR Gates / decisions lifecycle (pull_request) Successful in 31s
review-verdict/h10 Awaiting review verdict for d8bd1dc
Review verdict / Set review-verdict status (pull_request_target) Successful in 36s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m13s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 19s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 16s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m28s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m59s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Round-2 cross-family review returned BLOCKED: 2 High + 3 Medium. HIGH — here-strings traded one fail-open for another. `grep -q… <<< "$data"` fixes the SIGPIPE inversion, but bash materialises a large here-string via temporary storage, so it fails when temp space is full or unwritable — and since these sit inside `if`/`!`, that failure flips the predicate exactly as SIGPIPE did. It did NOT reproduce on my bash 3.2, DID on the reviewer's Linux bash 5.x, and CI is Linux; the disagreement is itself the argument for a construct that cannot fail either way. Path predicates now COUNT with `grep -c`, which drains stdin (no early exit, no SIGPIPE) over an ordinary pipe (no temp file), and grep's status is read honestly: exit 1 means "zero matches", a legitimate answer, while >1 is a real error that FAILS THE JOB rather than silently reading as "no match". `set -e` does not catch these on its own — they sit in command substitution inside a conditional. Verified correct under 171KB input AND an unwritable TMPDIR. The description test became a `case` prefix match, removing another pipeline from a security predicate. New record `ci.grep-q-pipefail-inversion` covers the whole class. HIGH — a human verdict landing mid-run was still overwritten, and the code claimed otherwise. The job read statuses once, classified over several round-trips, then posted: a reviewer posting BLOCKED in between had it replaced by an exemption `success`, turning an explicit rejection into a merge. Added a re-read immediately before the POST which refuses to write over a human verdict found then. The heading no longer says "never overwrite" — it cannot promise that, since there is no compare-and-set on Gitea's status API. Remainder tracked as #706. MEDIUM — documentation was stale in three places, all mine. The record's frontmatter `rule:` still listed the npm manifests (I fixed the body and forgot the frontmatter, so the canonical rule AND the generated catalog were wrong); docs/ci-cd.md still said `edited` was absent from `types:`, contradicting a section I had just updated; and the workflow header still implied the `edited` re-run settles the ABA race. All corrected to say detection, not atomicity. TESTS. 373 pass. New: a mid-run human verdict via a status stub that returns nothing on the first read and BLOCKED on the re-read, and large-input regression tests for the ADVISORY hook, which had none — the copy with less authority is the one that quietly keeps a bug (#649's whole point). Mutation-verified: reverting the hook predicate, the workflow predicates, or the pre-POST re-read turns exactly the intended tests red while every positive control stays green. Refs: #698 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bafb487eaa |
fix(698): review round 2 — close a PRE-EXISTING SIGPIPE inversion, narrow the bot allow-list, correct two overclaims
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
PR Gates / Docs update reminder (pull_request) Successful in 24s
PR Gates / decisions lifecycle (pull_request) Successful in 33s
review-verdict/h10 Awaiting review verdict for bafb487
PR Gates / Script tests (pytest) (pull_request) Successful in 59s
Review verdict / Set review-verdict status (pull_request_target) Successful in 34s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m30s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m36s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m9s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Cross-family adversarial review returned BLOCKED with 3 Blockers + 2 Highs. Two were verified independently before acting on them. BLOCKER 1 (pre-existing on main, not introduced here). `producer | grep -q…` inverts under `set -o pipefail`: grep exits at its first match, the producer takes SIGPIPE and exits 141 once the list exceeds the pipe buffer, so a MATCH is reported as a FAILED pipeline. Reproduced at 1901 paths / ~171KB, inside the enumerator's 2000-file cap: * `A.cs` + 1900 docs paths -> docs_only=yes, status 141 (a C# PR exempted) * `.gitea/…` + 1900 docs -> the PROTECTED guard MISSES entirely The second reaches a green required status on a PR rewriting the gate, with no retarget, no bot account and no forged status. This construct shipped with the original docs-only check (review-verdict.yml:257 on main), so it was live. Fixed in both the enforced gate and the advisory hook by using here-strings, which have no writer to signal. New record `ci.grep-q-pipefail-inversion`. BLOCKER 2 (mine). `web/package.json` / `web/package-lock.json` are removed from BOT_MANIFESTS. renovate.json enables only nuget/github-actions/dockerfile, so npm is unmanaged here and the entry bought nothing — while package.json `scripts` are EXECUTED by CI (npm ci, npm run build). It widened an exemption onto a code-execution path for no benefit. BLOCKER 3 + HIGH (documentation was wrong, code unchanged). The claim that `edited` made the retarget residual "non-durable" is retracted: runs are not serialized, so a stale run can post `success` after the reclassifying run posts `pending`. The ABA transition is narrowed and observable, NOT closed. Likewise the provenance check asks "posted by a user credential", not "posted by a reviewer" — ETV_STATUS_AUTH is basic auth, so a #697 forgery gets a non-null creator AND an attacker-chosen description and is preserved as human. Both now stated at full strength. TESTS. 4 large-input cases crossing the pipe buffer, each paired with a large-input POSITIVE control so "large lists now fail closed" (a deadlock) cannot pass as a fix. Verified by mutation: reverting the here-strings turns all three negatives red while the control stays green. Two of my own weak tests fixed — the "base advances" case called head_moves_to(SHA) with the already-current sha (a duplicate positive control, now a structural assertion that the comparator is .base.ref and never .base.sha), and the arity test counted five arguments without checking the fifth was the base. The whole class was invisible because every previous test used a handful of short paths: a guard whose behaviour depends on a buffer threshold needs a test that crosses it. Refs: #698 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f523fc535d |
fix(698): bind the base, constrain the bot exemption by content, re-derive unattributable successes
PR Gates / decisions lifecycle (pull_request) Successful in 24s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 27s
PR Gates / Docs update reminder (pull_request) Successful in 28s
Review verdict / Set review-verdict status (pull_request_target) Successful in 17s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m1s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m8s
review-verdict/h10 Review-verdict: BLOCKED @ f523fc5 (base: main)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 23m34s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The `review-verdict/h10` exemption path decided from mutable or unattributed PR state, and a machine-written `success` was never revalidated. Three routes, one root cause, so one change. Route 1 (reproduced live as probe PR #703, closed unmerged): `/pulls/{n}/files` diffs against the PR's LIVE base, so retargeting moves the answer without moving the head sha. A PR opened into `main` and retargeted mid-run enumerated docs-only and was granted `h10=success` while its diff against `main` carried a C# file; retargeting back reclassified nothing. `scripts/pr-changed-files.sh` now takes the expected base branch as a REQUIRED 5th argument (optional would be a silent opt-out) and checks it before and after paging; the workflow passes it from the `pull_request_target` payload, which a retarget cannot rewrite, and `edited` is in `types:` so a retarget reclassifies. A pinned two-sha diff would close route 1 outright but Gitea 1.25.4 cannot serve one: `compare/{base}...{head}` returns no `files`, and a `--depth=1` fetch of the two shas has no merge base. Measured, not assumed. The residual window is stated in the code and the record rather than papered over. Route 2: `pull_request.user.login` is the PR's immutable CREATOR while its head is not, so pushing code onto an open Renovate branch kept the exemption. The bot exemption now also requires EVERY path to be a dependency manifest — a set measured across all 11 Renovate PRs this repo has had, not guessed. Route 3: the never-overwrite short-circuit exited on ANY `success`, so a forgery obtained once was inherited forever. It now fires only for a status positively identified as a human verdict (non-null `.creator.login` AND a `Review-verdict:` description — measured: user-posted statuses carry a creator, Actions-posted ones carry null). Written in the positive direction so an unrecognised shape is re-derived rather than trusted. The two exemptions are composed, not chained: as an `elif` chain a Renovate docs-only PR lost the docs-only exemption. Caught before commit and pinned by a test. Tests: 17 new cases in scripts/tests/test_pr_changed_files.py, each verified by mutating the clause it covers (8 mutations, 8 kills). Both records trimmed under the 60-line prose ceiling so the corpus tail-boundary check stays calibrated. Does NOT close the class: anyone who can POST a status directly can still impersonate a verdict — that is #697, deliberately left open. Refs: #698 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0c492defac |
Merge pull request 'fix(672): trigger the verdict gate on pull_request_target scoped to main' (#699) from fix/672-review-verdict-head-resolution into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 16m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 20m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 23m5s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 9m39s
|
||
|
|
4e2ea61674 |
Merge pull request 'fix(691): guard the nullable SongMetadata.Artists/AlbumArtists at their read sites' (#700) from fix/691-song-artists-null-guard 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 / Build & test (.NET) (push) Successful in 17s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 31s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 33s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
|
||
|
|
ceef16081d |
docs(672): make the self-test gap discoverable (signals + section pointer)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
PR Gates / Script tests (pytest) (pull_request) Successful in 51s
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 17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m50s
review-verdict/h10 Review-verdict: MERGEABLE @ ceef160 (base: main)
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m57s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Re-review of the round-2 head returned MERGEABLE with one LOW: the record's
`signals:` field did not mention the self-test gap. That field is the semantic
discovery surface -- it is what the MemPalace mirror matches on -- so the most
operationally dangerous property of this change ("a gate edit goes live only on
merge, having never run") was unreachable by anyone searching for it. Someone
asking "how do I test a change to review-verdict.yml" would have found nothing.
Adds three signal phrases and points the record's one-line reference at a
section rather than at a ~1050-line file.
No behaviour change; frontmatter and prose only. Verified the frontmatter still
parses under PyYAML rather than the validator's hand parser, per #674 -- an
apostrophe in a single-quoted scalar is exactly what that hand parser cannot see.
Refs: #672
Decisions-Edit: yes
|
||
|
|
20b7171fba |
fix(672): review round 2 -- correct a stale rule: field, document the self-test gap
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 26s
PR Gates / Script tests (pytest) (pull_request) Successful in 52s
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 14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m22s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m49s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Re-review of the fix commits returned MERGEABLE (nine trigger mutations all caught, every prior finding verified against independent sources) with four low-severity findings. All four are addressed here. F1: `release.verdict-status-check`'s `rule:` frontmatter still said "A `pull_request` workflow auto-passes the two exempt classes". Round 1 past-tensed that record's BODY and left its `rule:` stale -- which is the exact failure mode the previous commit cites as the reason to put limitations in `rule:` in the first place. The catalog row mirrors this field verbatim and it mirrors again per-`key:` into MemPalace, so a stale `rule:` propagates further than a stale paragraph. F2: same record, "is what makes the rollout self-hosting" -> past tense. It described #630 and now reads as a live property. F3, the one that matters operationally: base resolution cuts BOTH ways. A change to `review-verdict.yml` is no longer exercised by its own PR -- the PR runs the version already on `main` -- so an edit goes live only ON MERGE, repo-wide, having never run. A broken edit merges green and then breaks the gate for every subsequent PR, and the PR that would repair it is gated by the same broken workflow. The recipe for verifying one safely (scratch base + throwaway PR + probe-named context) now lives in docs/ci-cd.md, which is where an operator looks, rather than in the record. F4: the sibling-workflow guard globbed `*.yml`, so a workflow added as `.yaml` would be silently unscanned. Latent today, which is when it is cheap. The record lost its meta-justification paragraph to the 60-line prose ceiling. Fifth trim this session; the operational recipe moving to ci-cd.md is better placement anyway, but it was forced rather than chosen. ersatztv#688. Refs: #672 Decisions-Edit: yes |
||
|
|
b2a5c72bfe |
docs(672): widen the residual to the real inventory (#697, #698)
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 19s
PR Gates / Docs update reminder (pull_request) Successful in 21s
PR Gates / decisions lifecycle (pull_request) Successful in 26s
PR Gates / Script tests (pytest) (pull_request) Successful in 43s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m52s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m59s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 24m4s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Cross-family review established the residual is materially larger than the previous commit said, and that saying "docker-build.yml / ETV_STATUS_AUTH" understates it. Gitea injects `GITEA_TOKEN` into EVERY job automatically, defaulting to read/write. So the set of workflows that can POST `review-verdict/h10` is not a short list to audit -- it is all of them, plus `workflow_dispatch` (1.24+ loads the definition from the selected branch) and `push`-triggered ones. A collaborator's own write-scoped API token is a route with no workflow at all, because branch protection binds the required CONTEXT, not its issuer. Recorded in #697. The same review found three defects in the exemption path itself, none of them introduced here and none closed here: a retarget race that enumerates a docs-only diff against a scratch base while the enumerator revalidates only head.sha, a Renovate-PR hijack (the exemption reads the immutable PR creator, not who pushed the head), and an inherited `success` that short-circuits before any PR/base/author/file check. Filed together as #698, since they share one root cause -- the gate trusts state it cannot attribute. Also drops the claim that the three properties are "pinned by tests". The tests pin the workflow's SHAPE; no in-repository test can establish status-authority isolation, and the sibling-workflow guard added in the previous commit catches only a workflow that names the context in plain text. Trimmed to the 60-line prose ceiling for the third time in this session. That is ersatztv#688 -- the ceiling is now deleting rationale two reviewers asked for. Refs: #672 Refs: #697 Refs: #698 Decisions-Edit: yes |
||
|
|
dd7b58232c |
fix(691): revert entity-level null guard, guard read sites instead
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 59s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m54s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m4s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 11s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
review-verdict/h10 Review-verdict: MERGEABLE @ dd7b582 (base: main)
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 22m12s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Review verdict / Set review-verdict status (pull_request) Successful in 5s
PR Gates / decisions lifecycle (pull_request) Successful in 13s
PR Gates / Script tests (pytest) (pull_request) Successful in 46s
The prior commit (
|
||
|
|
35a8ea8aef |
fix(672): review round 1 -- pin the trigger set exactly, sweep the stale claims
Cold review found the first cut of the test satisfiable by a still-vulnerable
config, and two prose claims that outran the evidence.
The test asserted "pull_request_target present, pull_request absent". Adding
`workflow_dispatch:` or `push:` ALONGSIDE it kept that green, and both are
ref-resolved with secrets, so either one restores an equivalent
self-supplied-definition path. Enumerating those two would have the same hole one
trigger later, so the assertion now pins the whole set: exactly
{pull_request_target}, nothing else. Verified by mutation -- adding
`workflow_dispatch` now reds.
Adds the guard that would have caught the residual below rather than only the
instance: no workflow OTHER than review-verdict.yml may reference
`review-verdict/h10` in executable lines. Scoped honestly in its docstring as a
drift guard, not a security boundary -- a workflow can still write the status
through an indirection a text scan cannot see.
`release.verdict-status-check` item 4 still asserted, in the present tense, that
a PR editing review-verdict.yml is judged by its own edited copy. That is now
false for this workflow, and it is the record a reader resolving the gate from
the catalog actually lands on. Past-tensed, with the surviving residual named.
The probe count said three. There were four; the omitted one is the only one with
a negative result, which is what turns an honest partial into an overclaim.
Corrected in both the record and ci-cd.md, along with what was NOT measured
(`reopened`/`ready_for_review` firing under the new trigger).
Also records two operational consequences a maintainer will otherwise hit cold:
retargeting a PR onto `main` leaves it statusless until its next push (`edited`
is not in `types:`), and the required contexts carry a literal `(pull_request)`
suffix, so repeating this fix on docker-build.yml would rename them and deadlock
merges unless branch protection is edited in the same operation.
Trimmed the record back under the 60-line prose ceiling -- for the second time
this session, which is ersatztv#688 reproducing, not a defect here.
Refs: #672
Refs: #697
Decisions-Edit: yes
|
||
|
|
8b73234d78 |
docs(672): record that the fix closes the route, not the class (#697)
Probing rather than reasoning turned up a second instance of the same vulnerability class while this fix was in review. `docker-build.yml` also triggers on `pull_request`, so it is head-resolved too, and it carries `ETV_STATUS_AUTH` (`REGISTRY_USER:REGISTRY_PASSWORD`) for the #420 revalidation read. Basic auth is not scoped: an account that can read commit statuses can write them. Confirmed with a scratch PR that POSTed a probe-named context using those credentials and succeeded — so a PR rewriting `docker-build.yml` can still post `review-verdict/h10=success` for its own head. That workflow cannot take the same fix. It builds and tests the PR's code, so it must resolve from the head; `pull_request_target` there would be the real footgun. It needs a read-only status identity instead. Filed as #697. The `rule:` field carries the limitation, not just the body, because the predecessor record's documented failure was exactly a reassuring sentence in the position a catalog reader stops at. Also trims the record to the 60-line prose ceiling. Adding it at 62 lines pushed p90 past the ceiling and reddened the blocking `script-tests` job — which is ersatztv#688 reproducing live, not a defect in this change. Refs: #672 Refs: #697 Decisions-Edit: yes |
||
|
|
a4700185b2 |
fix(691): guard SongMetadata.Artists/AlbumArtists at the domain boundary
SongMetadata.Artists and .AlbumArtists are nullable EF primitive collections that FallbackMetadataProvider.GetSongMetadata never assigns, so untagged songs persist them as NULL. SongVideoGenerator dereferenced both unguarded (metadata.Artists.Count, string.Join, AlbumArtists.Filter(...Artists.Contains...)), throwing NRE/ANE during song-video generation on the playback path. Rather than enumerating and guarding each read site (the same mistake that left these unswept after #671), add backing fields to the two properties whose getters coalesce null to an empty list. EF Core's default PreferField access mode reads/writes the raw backing field during materialization and change-tracking (confirmed by running the full ErsatzTV.Tests suite, including SongMetadata round-trip tests, unchanged), while every other caller -- SongVideoGenerator, MediaCollectionRepository's rerun-collection artist grouping, and any future reader -- goes through the property getter and always sees a non-null list. This subsumes the ad hoc `metadata.Artists ??= []` guards already hand-applied in LuceneSearchIndex/ElasticSearchIndex and the `?? []` in LibraryBrowseItemMapper, which remain but are now redundant. Adds SongVideoGeneratorTests covering an untagged song (null Artists/ AlbumArtists) through GenerateSongVideo; verified RED (NRE at SongMetadata.cs's Artists getter) by reverting only the `??= []` clause, not the file. Strips the pre-existing UTF-8 BOM from SongMetadata.cs per the #311 formatting gate (touching a legacy-BOM file makes stripping it ours to do). Refs #691 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cf907f0988 |
fix(672): trigger the verdict gate on pull_request_target scoped to main
`review-verdict.yml` writes `review-verdict/h10`, the status branch protection requires on `main`. It triggered on `pull_request`, and Gitea resolves a `pull_request` workflow DEFINITION from the PR's own head commit — so a PR editing this file ran its own rewritten copy and could post `h10=success` for itself. `PROTECTED` did not help (the rewrite defines it too), and neither did branch protection, which requires the context rather than an author and carries `required_approvals: 0`. The #649 base-ref checkout binds the SCRIPTS this job runs to the base. It cannot bind the job definition. Only the trigger can. Measured on this instance (Gitea 1.25.4) with three throwaway scratch PRs rather than inferred from GitHub, since the premise is a claim about someone else's software: A. A PR rewriting the workflow posted its own probe context and the real `review-verdict/h10` was never written — the base definition never ran. B. Under `pull_request_target` the identical rewrite was ignored: the BASE definition ran and posted `h10=pending`, on `opened` and `synchronize` alike, with `secrets` still available. C. With `branches: [main]`, a PR into a non-main base produced no run and no status at all. The probes only ever posted probe-named contexts, never a forged `h10`. `branches: [main]` is half the fix, not a refinement: base resolution means the BASE branch supplies the definition, so without it the rewrite simply moves to an attacker-pushed base — and a status forged there is inherited by any later real PR with the same head sha (#663). `pull_request_target` is safe here only because this job never checks out or executes head-supplied code; the base-ref checkout is what makes the trigger usable, so the two are one decision. Rejected `required_approvals: 1` as the cheaper fix: Gitea forbids approving your own PR and this is effectively a single-maintainer repo, so it would deadlock every PR rather than gate the dangerous ones. Three mutations confirm the new test discriminates rather than merely passing: reverting to `pull_request`, dropping the `branches` filter, and re-adding `pull_request` alongside the safe trigger each go red with a distinct message. It parses the YAML instead of substring-matching because `pull_request` is a prefix of `pull_request_target`. Refs: #672 Decisions-Edit: yes |
||
|
|
036bcfc5a0 |
Merge pull request 'fix(671): resolve rerun-collection selections through one shared include chain' (#692) from fix/671-rerun-collection-selection into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 16s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 27s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 28s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
|
||
|
|
2249a806c9 |
fix(671): review round 4 -- fix the chapter-title entity interpolation
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 22s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 29s
PR Gates / Docs update reminder (pull_request) Successful in 41s
PR Gates / decisions lifecycle (pull_request) Successful in 41s
Review verdict / Set review-verdict status (pull_request) Successful in 15s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m17s
PR Gates / Script tests (pytest) (pull_request) Successful in 1m11s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 20m1s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 22m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 25m35s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
review-verdict/h10 Review-verdict: MERGEABLE @ 2249a80 (base: main)
Final cold review of
|
||
|
|
34eee753b2 |
fix(671): review round 3 -- sweep Artists by FIELD, correct the archaeology
Cold review of
|
||
|
|
572737a29e |
fix(671): review round 2 -- guard Song.Artists, cover the second consumer
Cold independent review of
|
||
|
|
017ef988d0 |
fix(671): review round 1 -- pin exact names, complete the playlist include
Cross-family (Codex) adversarial review of
|
||
|
|
8523088ceb |
fix(671): resolve rerun-collection selections through one shared include chain
The paged list handler eager-loaded nothing, so `ProjectToViewModel` read four unloaded navigations and every row of every collection type projected a null selection. Because the selected id and the display name are read off the SAME navigation, this dropped the id too -- the harm is not an unlabelled badge but an editor that round-trips a null and clears the user's stored selection. The by-id handler loaded metadata for only four of the ten selectable media types: Song/OtherVideo/Image/RemoteStream returned a null-ish selection and Episode/MusicVideo threw an NRE that surfaced as a 500. Fixed at the boundary rather than per call site: - `RerunCollectionQueryExtensions.IncludeSelectionDetails()` is now the single include chain, called by both handlers, joining the existing `ProgramScheduleItemQueryExtensions.IncludeScheduleItemDetails()` precedent (#229). Artwork legs are deliberately omitted -- this projection reads only ids and titles. - The media-item switch was duplicated verbatim for RerunCollection and PlaylistItem; both now call one `ProjectMediaItemToViewModel`, which handles `RemoteStream` (via a new `ProjectToNamedViewModel`, since the existing `ProjectToViewModel(RemoteStream)` returns an unrelated type) and never falls through to null -- an unknown subtype keeps its id and takes a conspicuous name, because throwing would fail a whole paged GET over one bad row. - Every metadata navigation in `MediaItems.Mapper` is now read through `Optional(...).Flatten()`, so an un-included nav degrades to "???" instead of being a latent 500 for whichever caller loads least. Tests enumerate all 13 supported CollectionTypes for both handlers, with the matrix derived from `IsSupportedSelectionType` so a newly-supported type joins it automatically, plus a completeness guard on the set. Each mechanism was removed in turn and confirmed red first. fixes #671 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d4ea1584c0 |
Merge pull request 'fix(668): reach accented facet values via a registered Unicode fold on SQLite' (#687) from fix/668-accented-facet-values into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 33s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 34s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m17s
|
||
|
|
f2d9c0dc8e |
fix(668): review round 5 -- three prose nits, including an off-by-one I filed
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 14s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 21s
Review verdict / Set review-verdict status (pull_request) Successful in 15s
PR Gates / decisions lifecycle (pull_request) Successful in 26s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
PR Gates / Script tests (pytest) (pull_request) Successful in 53s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m0s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 14m33s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
review-verdict/h10 Review-verdict: MERGEABLE @ f2d9c0d (base: main)
Final sweep confirmed the retracted MySQL over-match claim survives in no file
on the branch (only in two immutable commit messages, which stay -- rewriting
history would invalidate every sha-bound review verdict). Three nits remained.
- The fixture's class docstring said the on-MySQL claim "rests on the server's
collation", which is the one thing the decision record says it does NOT rest
on. It rests on Unicode-aware LOWER(); the executed comparison bypasses the
collation entirely. Reworded.
- The record's `rule:` enumerated the covered fields but omitted show_genre,
which GetSource and the fold both handle ("genre" or "show_genre"). Added.
- My own #688 write-up was wrong twice: the 60-line ceiling warning is
NON-blocking by design, and the calibration pytest reds at >=61, not >=60 --
main's p90 is 59, so a 60-line record makes p90 == ceiling and PASSES. The
bullet even contradicted itself, since the next sentence relies on 60 passing.
Corrected in the PR body and in the issue.
Decisions-Edit: yes
|
||
|
|
07723e418b |
fix(668): review round 4 -- sweep the retracted claim by SUBJECT, not by memory
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 13s
PR Gates / Docs update reminder (pull_request) Successful in 21s
PR Gates / decisions lifecycle (pull_request) Successful in 25s
PR Gates / Script tests (pytest) (pull_request) Successful in 44s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 34s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 19s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m55s
Review verdict / Set review-verdict status (pull_request) Failing after 10m59s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m54s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Round 3 said "correct the claim everywhere" and missed two places, which is the same mistake in a new coat: I fixed the spots I remembered instead of grepping for the subject. - The class-level summary of the very file round 3 edited still said MySQL's "ci collation OVER-matches instead", contradicting the method docstring forty lines below it. Reworded. - The PR body still carried the retracted over-match story -- and round 3's commit message claimed it had been corrected. It had not. Now corrected, with the measurement table and the retraction stated openly. This time the sweep was `grep -i over-match` across every file the branch touches; the remaining hits are the SQLite-fold invariant and #578 history, which are correct and stay. Also softened two overclaims the reviewer flagged. Round 3 deleted the predecessor's "configuration-incidental, not designed" hedge and replaced it with a firmer statement than the evidence supports: that MySQL cannot over-match is contingent on MySqlConnector fixing the connection collation to utf8mb4_bin, not a property of MySQL. A driver, protocol or prepared-statement change could restore it. The record and the fixture docstring now say "driver-contingent, not a law"; the code is safe either way because the ordinal filter stays regardless. Filed #689 for the source of the copied falsehood -- LibraryFolderDedupeMigrationTests on main carries the same "CI sets ETV_REQUIRE_MYSQL_TESTS=1" sentence. Decisions-Edit: yes |
||
|
|
dda98efcc4 |
fix(668): review round 3 -- MySQL does NOT over-match; correct the claim everywhere
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
PR Gates / Docs update reminder (pull_request) Successful in 17s
review-verdict/h10 Awaiting review verdict for dda98ef
PR Gates / decisions lifecycle (pull_request) Successful in 25s
Review verdict / Set review-verdict status (pull_request) Successful in 13s
PR Gates / Script tests (pytest) (pull_request) Successful in 55s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m32s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m24s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m18s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Review BLOCKED on a false CI claim I copied from the sibling fixture ("CI sets
ETV_REQUIRE_MYSQL_TESTS=1"). Nothing sets it; the doc now says plainly that CI
does not arm this lane and points at ersatztv#627. That was the blocker.
Chasing the reviewer's second finding then overturned something bigger. It
predicted that seeding an unaccented "Edith" would make the in-memory ordinal
filter load-bearing on MySQL, since utf8mb4_0900_ai_ci treats é as e. Mutation
test says otherwise: with the filter deleted the MySQL test stays GREEN.
Measured against a live 8.4 to find out why:
LOWER(Name) LIKE 'é%' (literal) -> Édith AND Edith
LOWER(Name) LIKE @v (ai_ci variable) -> Édith AND Edith
LOWER(Name) LIKE @v COLLATE _bin -> Édith only
the EF query, executed -> Édith only
The driver binds the pattern with a BINARY collation, so the executed
comparison is accent-SENSITIVE and MySQL does not over-match at all. MySQL's
correctness rests on its Unicode-aware LOWER(), not on the collation.
My earlier probe used a LITERAL pattern -- a different query from the one the
code runs -- and I wrote its result into the handler comment, the decision
record and the PR body. All three now say what actually happens, and the record
carries the lesson: measure the query the CODE runs, not one you type.
The "Edith" row stays as a near-miss control, with a docstring that says what it
does and does not prove rather than the over-match story it was added for.
Also moved EnsureCreatedAsync out of [SetUp]: NUnit skips [TearDown] when
[SetUp] throws, so a mid-create failure would strand the database.
Decisions-Edit: yes
|
||
|
|
1f6802bb62 |
test(668): execute the accented-value claim on a REAL MySQL server
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 14s
PR Gates / Docs update reminder (pull_request) Successful in 16s
review-verdict/h10 Awaiting review verdict for 1f6802b
PR Gates / decisions lifecycle (pull_request) Successful in 27s
PR Gates / Script tests (pytest) (pull_request) Successful in 45s
Review verdict / Set review-verdict status (pull_request) Successful in 17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m28s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m30s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m43s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 15m48s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m23s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The PR's central claim is "reachable on BOTH providers", and on MySQL that rests on the server's collation rather than on any code this repo owns -- exactly the kind of assumption worth executing rather than asserting. Adds SearchFieldValuesProviderTests, parameterized over Sqlite and MySql on the LibraryFolderDedupeMigrationTests contract: opt-in via ETV_TEST_MYSQL_CONNECTION, a VISIBLE skip without it, and a hard failure instead of a skip when ETV_REQUIRE_MYSQL_TESTS is set, so the lane cannot degrade into "connected to nothing and passed". Fresh never-reused database per test, dropped with its pool cleared. Verified for real, not just written: run against a live mysql:8.4 it reports 4 passed / 0 skipped and the stored 'Édith' is returned for both q=é and q=É. Without the connection string it skips (2 skipped); with REQUIRE set and no connection it FAILS. All three paths exercised. The MySql half wires RegisterUnicodeCaseFunctions to an explicit no-op, so the test proves MySQL reaches the value through its own Unicode-aware LOWER() and not through SQLite's custom fold. Known and deliberate: CI does NOT arm this lane, so it will skip there. Per the note in docker-build.yml, running MySQL fixtures against the live service was implemented and removed as non-deterministic (ersatztv#627) on the grounds that an intermittently-red gate is worse than none. Re-arming it is that issue's job, not this PR's -- so this fixture is opt-in exactly like its sibling. |
||
|
|
7ca058f83b |
fix(668): review round 2 -- remove a second false comment, harden a vacuous assertion
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 19s
PR Gates / Docs update reminder (pull_request) Successful in 20s
PR Gates / decisions lifecycle (pull_request) Successful in 26s
PR Gates / Script tests (pytest) (pull_request) Successful in 46s
Review verdict / Set review-verdict status (pull_request) Successful in 1m6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 21s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m32s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 15m45s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
review-verdict/h10 Review-verdict: MERGEABLE @ 7ca058f (base: main)
Re-review of the round-1 fix commit returned MERGEABLE with three findings, all about claims rather than behaviour. All three applied. 1. A stale FALSE parenthetical survived round 1. The docstring on Unicode_Fold_Agrees_With_The_Ordinal_Filter still claimed it catches "one that stops filtering the extras out". It does not. Mutation-verified: delete the Where in FilterSortTake and all EIGHT cases stay green, because each is either a positive SQL alone returns or an ASCII-query negative SQL alone rejects. The same mutation turns the new over-match test RED, so the pair does cover both directions -- but only the corrected wording says so. This is the same species of error round 1 fixed, one paragraph above it; swept by subject this time. 2. Unicode_Fold_Over_Match_Is_Discarded_By_The_Ordinal_Filter asserts an EMPTY result, so it passes vacuously if the fold never runs. Its premises are now asserted explicitly (the query is non-ASCII, and ToUpperInvariant maps ſ to S), so a fold that quietly stopped mapping ſ would fail rather than go green for the wrong reason. 3. The comment on IsSqlite overstated its enforcement. ProviderStaticsWiringTests parses the composition roots for ASSIGNMENTS only; nothing mechanically stops a read of TvContext.IsSqlite here. The real reason stands -- such a read would falsify that test's prose exemption while the test stayed green -- so the comment now says that instead of implying a guard that does not exist. Decisions-Edit: yes |
||
|
|
ce215be590 |
chore(668): arm the Decisions-Edit trailer, which the earlier commits voided
The two preceding commits both END with:
Refs #668
Decisions-Edit: yes
`Refs #668` has no colon, so git does not recognise it as a trailer -- and a
single non-trailer line in the final paragraph voids the WHOLE block, taking
the valid `Decisions-Edit: yes` with it. Confirmed with interpret-trailers:
the pair parses to nothing, while `Decisions-Edit: yes` alone (or `Refs: #668`
with a colon) parses fine. `%(trailers:key=Decisions-Edit,valueonly,unfold)`
-- exactly what scripts/decisions_validate.py reads -- returned empty, so the
`decisions lifecycle` job failed for a real reason, not the known flake.
The branch is already pushed, so amending is out (process.pr-routine-sequence).
The guard accepts the trailer on ANY non-merge commit in the merge-base range,
so this empty commit carries a well-formed one. It is deliberately empty rather
than bundled onto an invented change.
Decisions-Edit: yes
|
||
|
|
ac67c9ee74 |
fix(668): review round 1 -- make two guards actually guard
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 17s
review-verdict/h10 Awaiting review verdict for ac67c9e
PR Gates / decisions lifecycle (pull_request) Failing after 30s
PR Gates / Script tests (pytest) (pull_request) Successful in 43s
Review verdict / Set review-verdict status (pull_request) Successful in 43s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m31s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m32s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m57s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 15m44s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Independent review found both new guard tests weaker than they read. 1. A false docstring. It claimed the SQL stage "genuinely returns 'ſweet' for q='S'". It does not: 'S' is ASCII, so ContainsNonAscii is false and the fold branch is SKIPPED. Those three negative cases exercise the ASCII fast path, which is worth pinning but is not what the comment said -- and the consequence was that NO test drove a row through the fold for the ordinal filter to discard, i.e. the harmless over-match direction the whole design rests on was untested. Comment corrected and Unicode_Fold_Over_Match_Is_Discarded_By_The_Ordinal_Filter added (stored "Sword", q="ſ" -> fold runs, SQL pattern S%, SQLite returns the row, filter drops it, response empty). 2. Unicode_Fold_Escapes_Like_Wildcards could not fail if the %/_ escaping it names were deleted -- the in-memory filter masks the over-match, so the counts stay right. The escaping's real role is preventing LIMIT crowding, so Unicode_Fold_Escaping_Prevents_Limit_Crowding pins that instead. Verified by mutation: with the %/_ replaces removed the new test fails while the original two still pass. Also: the crowding residual in the decision record was attributed to MySQL alone; the SQLite fold shares it in principle, so "no accepted loss" is narrowed to mean no unreachable VALUE rather than a guaranteed count. And a comment says why the provider check is derived per-context instead of reading TvContext.IsSqlite (that static is scoped host-only by ProviderStaticsWiringTests, and reading it here would falsify the exemption). Refs #668 Decisions-Edit: yes |
||
|
|
05542946ad |
fix(668): reach accented facet values via a registered Unicode fold on SQLite
SQLite's LOWER() folds ASCII only -- lower('Édith') is 'Édith' unchanged --
so the EF-sourced facet fields UNDER-matched any stored value whose prefix
carries an uppercase non-ASCII character. An under-match is unrecoverable:
no later stage can reintroduce a row SQL never returned.
Adds a SECOND, ADDITIVE query taken only when the provider is SQLite and q
contains a non-ASCII character: raw Dapper SQL folding through etv_upper(),
a SqliteConnection.CreateFunction scalar implementing ToUpperInvariant.
Every other case -- all-ASCII q, and MySQL for all q -- runs the existing
EF query byte-identically.
MySQL needed no change and gets none: verified on MySQL 8.4 that its LOWER()
is Unicode-aware and its ci collation makes the predicate OVER-match, which
the existing ordinal filter already discards.
The fold is ToUpperInvariant because OrdinalIgnoreCase equality is a strict
SUBSET of invariant-uppercase equality, so the SQL stage yields a superset of
the final filter's matches and can never under-match. Note OrdinalIgnoreCase
is NOT "invariant-upper then ordinal": ToUpperInvariant('ſ') is 'S', yet
"ſweet".StartsWith("S", OrdinalIgnoreCase) is false. Tests pin that.
No migration, no model change; both provider snapshots are untouched.
Refs #668
Decisions-Edit: yes
|
||
|
|
61aa8a902a |
test(668): red-first pin for accented values on EF-sourced facet fields
Stored 'Édith' is unreachable from q=é and q=É on SQLite, because SQL LOWER() folds ASCII only. The stored-LOWERCASE pair is pinned alongside it and passes today, so the fix must supplement that path, not replace it. Red: both uppercase cases return []. Refs #668 |
||
|
|
aa1f504e02 |
Merge pull request 'fix(684): key the pageSize guard registry on identity, not source position' (#686) from fix/pagesize-guard-line-churn into main
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 & test (.NET) (push) Successful in 35s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 35s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 6m6s
|
||
|
|
689451161e |
fix(684): review round 2 -- drop a false exhaustiveness claim I introduced
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 14s
PR Gates / Docs update reminder (pull_request) Successful in 16s
Review verdict / Set review-verdict status (pull_request) Successful in 7s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
PR Gates / Script tests (pytest) (pull_request) Successful in 51s
review-verdict/h10 Review-verdict: MERGEABLE @ 6894511 (base: main)
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m33s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 27s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 18s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The L1 fix (name every class-b shape) collided with the M1 fix (move SmartCollectionDialog into class-b) in the same commit: the comment claimed "exactly three shapes, which is the whole list" while the registry 80 lines below already held four. That is the same false-exhaustiveness defect this PR exists to remove -- a reader adding a fifth class-b site would conclude theirs cannot be class-b despite rendering a real totalCount hint, and reach for search-bounded or deviation. The operative rule is now stated first and the shape list is explicitly illustrative: a site qualifies by RENDERING a totalCount-backed hint, not by resembling a listed shape. Also, both non-blocking review items: - the deviation prose said the tracking issue goes "in the note", while enforcement is on the structural `issue` field -- it now points at the mechanism that actually binds, and says why the note scrape was rejected; - the UNREGISTERED report prints every position sharing an identity, so it now says "identity seen at:" rather than implying all of them are unregistered. A positionless key cannot know which occurrence is excess; the candidate set is the honest answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fc8353c75c |
fix(684): key the pageSize guard registry on identity, not source position
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
PR Gates / Docs update reminder (pull_request) Successful in 21s
review-verdict/h10 Awaiting review verdict for fc8353c
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 25s
PR Gates / decisions lifecycle (pull_request) Successful in 34s
Review verdict / Set review-verdict status (pull_request) Successful in 31s
PR Gates / Script tests (pytest) (pull_request) Successful in 44s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m42s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 16m38s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m16s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The #650 guard pinned every pageSize call site by absolute line:column, making the registry a function of every OTHER file in the repo. The guard was BORN RED. #651 moved AutoTuneScreen.tsx up ten lines and FillerPresetsScreen.tsx down seventy-two, and merged to main BEFORE the guard's own PR (#675) did -- so the registry, authored against a pre-#651 base, was stale the instant it landed. Its own merge run was CANCELLED, so nothing reported it; the red first surfaced on the next push (#676's merge, which touches no web/src file and is not the cause). One ordering accident, not a recurring pattern -- but the exposure is general, because every PR is green against its own base and the breakage exists only in the merge result. Identity is now (file, kind, value). New/removed/changed sites all still fail. The MULTISET comparison is preserved, so a shared identity must be discovered exactly as many times as it is registered. The scanner's positional pageSizeSiteId is untouched: pageSizeScan.test.ts asserts real AST positions against fixed fixtures, the opposite case, with no churn to remove. The one case this costs is stated rather than implied: a same-identity substitution within one file (delete a registered site, add a different unreviewed one with the same kind and value token) now passes. Narrow, and caught only incidentally before. Named in the guard and the record because "costs no coverage" is a claim that outlives whoever made it. Failure reports still print the discovered line:column -- identity and diagnostics need not share a format, and a bare id was useless in a file holding two such sites. Registry reconciled with #651: Playlists/RerunCollections lost their bounded windows to the shared searchLibraryPickerOptions, now registered in its place. Adds a 'search-bounded' class for that shape. Adds a 'deviation' class rather than laundering a live defect into a compliant-looking label. Reconciling the registry surfaced a §3b violation (#685, filed): CollectionsScreen's AddItemsDialog degrades to an unfiltered whole-type window on an empty query and surfaces nothing. Both existing labels would have been false, and either would have made the guard vouch for behaviour that does not exist. Deviation entries must name a tracking issue, enforced by a structural field -- a #\d+ scrape of the note passed with the reference deleted, because notes legitimately cite historical issues. Corrects SmartCollectionDialog to class-b: it does render a totalCount badge, which is class-b's defining evidence. fixes #684 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ac0f65c743 |
Merge pull request 'docs(649): narrow the base-ref headline to what the checkout actually binds' (#683) from docs/649-narrow-base-ref-headline into main
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 / Functional E2E (curl + UI contracts) (push) Successful in 16s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 47s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 1m4s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 9s
|
||
|
|
c794a48462 |
docs(649): narrow the base-ref headline to what the checkout actually binds
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 24s
review-verdict/h10 Exempt: docs-only change (no code, no protected path)
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 30s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Review verdict / Set review-verdict status (pull_request) Successful in 17s
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 13s
PR Gates / Script tests (pytest) (pull_request) Successful in 52s
The record's bold sentence said the base-ref checkout means the workflow "cannot be rewritten by that same PR to weaken its own judgment". That is true of the SCRIPTS the job executes and false of the workflow itself: Gitea resolves a `pull_request` workflow definition from the PR's own head, so a PR editing `review-verdict.yml` runs its rewritten copy — which can delete the checkout outright, or just post `review-verdict/h10=success` and stop. Branch protection requires the context, not an author, and carries `required_approvals: 0` (#672). The scoping already existed further down, under "What is deliberately NOT claimed". That is not good enough for this particular sentence: it is bold, it is the paragraph a reader resolving this record from the catalog lands on, and someone who stops there leaves with the opposite of the truth. A caveat only works if it is reached. So the headline now says what the checkout binds (the scripts, from the already-reviewed base) and a following paragraph states the head-resolution hole directly, with the superseded claim quoted so a reader who remembers it can see it was retracted rather than wonder whether two records disagree. The later paragraph loses its duplicated opener and points at it instead. Docs only; no behaviour change. Same failure class the rest of #649 kept turning up — a claim stronger than the code — reached this time through prose rather than a test. Refs #649, #672 Decisions-Edit: yes |
||
|
|
aeff810cad |
Merge pull request 'test(649): cover the review-verdict status read and the bot-path guards' (#673) from test/649-workflow-body-coverage into main
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 & test (.NET) (push) Failing after 1m41s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
|
||
|
|
cb7da865b6 |
Merge pull request 'docs: permit subagents explicitly, and make claiming an issue a check rather than a label' (#682) from docs/claim-protocol-and-subagents into main
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 29s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 41s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 43s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
|
||
|
|
1d76a088c6 |
Merge pull request 'feat(578): artist typeahead covers music-video and song credits; album_artist stops 404ing' (#676) from feat/578-artist-typeahead-source into main
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m58s
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 19m9s
Build ErsatzTV Image / Build & test (.NET) (push) Failing after 3m39s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been skipped
|