b1d5fbefcba02fdc6c19fef85cec1c4e82fc8dea
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
595c819de1 |
fix(916): a failed substitute is an error, not a landed claim; head_sha described in both schemas; the issue's box says what the code does
The runner-beside-the-lenses design is now what Done-when box 1 asks for (body amended). A rubric round whose runner and worktree fallback both fail returns an error before the push instead of landing a PR whose body claims a substitute reviewed it. The harness records lens count at the runner's start too (expects 0, so a re-serialised runner reddens), resets its counter per round, and has a case for the double failure. Decisions-Edit: yes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV |
||
|
|
929dff835a |
fix(916): the Codex runner runs beside the lenses, only the fallback waits; head_sha described everywhere; the docs keep their thresholds
The runner builds nothing, so serialising it only added its wait to the critical path; the worktree-isolated fallback is what must follow the lenses, and the harness case now records lens count at the FALLBACK's start alone. setTimeout in the harness is globalThis.setTimeout (the .mjs lint config has ES builtins only). head_sha carries the same description in both scripts and every fixer/implementer prompt asks for the worktree HEAD, not a PR head. The mechanics page says why the cap stays at one after the serialisation and restores the 20%/10% RAM thresholds by key; the record says "several", not "three". Decisions-Edit: yes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV |
||
|
|
a0203f2863 |
fix(907): round two — a guard denial means a foreign worktree, required keys under every branch, a gated post-rebase round
The marker-overwrite instruction asserted an antecedent no agent can verify and, since same-session worktrees carry no marker, could only fire in another session's worktree; the scripts now stop and report. `ran` and `patch_changed` move into required keys of their own schemas so a missing field cannot read as a successful cross-family review or an unchanged patch. A blocking finding in the post-rebase round now returns an error like every other failure path. Each reviewer lens gets its own E2E port; the gate text travels with the reviewer brief. The standing prompt no longer contradicts the substitution the scripts perform; the record names patch-id, the mechanism the scripts use. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV |
||
|
|
63256afd96 |
fix(907): answer the cold review — drop the E2E lock, scope force-with-lease in a record, referee ticks the boxes
The mkdir lock around scripts/e2e-local.sh serialised the launch, not the run (the launcher returns with the server up), and its stale-holder path double- acquired in 4 of 91 measured races; the launcher's documented conflict is its per-worktree wwwroot, so slots now run on their own port and the lock is gone with its inventory row. process.orchestrated-session records the two scopings the harness needed: a rebase pushed with --force-with-lease as the one sanctioned rewrite, and the referee as the only agent that ticks Done-when boxes. Scripts: required-arg guard, per-issue claim probe, reviewer fetch recipe, codex fallback to a cold review-only agent with the substitution stated in the PR body, rebase before the review loop with a patch-id check at the push, non-interactive squash recipe, Land phase. README bullets re-parented; kickoff bullet keyed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV |
||
|
|
8fd9eae0bf |
fix(891): a sourced path is code, so every hook resolves it from its own tree (#903)
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 12s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 30s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m12s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m3s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m10s
Every hook under `.claude/hooks/` assigned `ETV_HOOK_FIRE_LIB` from `${CLAUDE_PROJECT_DIR:-<self>}`
and then `. `-SOURCED it. Sourcing is execution, so a file of that name in an env-designated tree ran
as code inside the hook before stdin was read and before it could decide anything. Measured on the
merge gate before #858 fixed that one hook: a decoy tree's copy printed an `allow` and exited 0.
Reachable without an attacker, because husky is a different launcher: `.husky/pre-push` invokes
`./.claude/hooks/…` relative to the PUSHED tree, independent of the variable, so a push from one
worktree while the environment names another sources the other tree's code into a gate.
Sweeps the remaining twelve hooks together (population derived from `git ls-files`), reconciles the
second resolution inside `scripts/hook-fire-log.sh` itself, and requires the root to OWN the sink
(`-ef`, not `-e`). The static guard pins the preamble BYTE-FOR-BYTE — a withdrawal, after a lexical
rule was defeated by five successive shapes.
Also pins two arms of the checker that were unsubsumed AND unpinned: the begin call's presence and
its missing stdout-mode token. `…_LOSES_its_instrumentation_…` looked like their proof and was not —
it asserts only that the fault list is non-empty, and a stripped hook trips four arms, so deleting
either left the suite green.
fixes #891
refs #858, #859, #776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UYNbVwgVszv6Pum7ZuGd75
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
|
||
|
|
cf5f42edf9 |
fix(858,859): a rule the classifier cannot read is not a rule that matches nothing (#897)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 10s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 27s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 10m49s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m41s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 7m18s
Build ErsatzTV Image / Build & push image (amd64) (push) Failing after 1m26s
#859 was filed as a wrong STATED CAUSE. It was masking a live false-open in the merge gate. Gitea reports a GLOB branch-protection rule with an EMPTY `branch_name` — the canonical name lives only in `rule_name`. Measured 2026-08-30 on a scratch repo against 1.27.1. jq's `//` fires on null and false but NOT on `""`, so `(.branch_name // .rule_name // "")` resolved every glob rule to the empty string — a name with no metacharacters — and the glob test, the entire basis of the classifier's undecidable-first ordering, never saw it. Measured on the predecessor: glob `m*` (not requiring review-verdict/h10) beside plain `main` (requiring it) resolved to `exact` on `main` and AUTO-GRANTED a scheduled merge, while Gitea — ordering by Priority then plain-name-ness — may be applying `m*`. That is #622's hole, reached through the ordering written to close it. Mirror case: a glob alone resolved to `none` and DENIED about a rule that provably governs the base. A name is now a non-empty string. Each field resolves to a NAME, a SKIP (absent/null/ empty — fall through), or POISON (present, wrong type — poisons whichever field carries it). A rule with no usable name is a distinct `unreadable` verdict with its own operator cause, instead of feeding `none`, whose whole authority is "the full rule list was read and none matches". The short-circuit is STRUCTURAL: jq binds `as` eagerly, so the flat form still evaluated `offs`/`nonascii` on the bad name and died before reaching the arm meant to prevent that. Also #859: `branch_protections` is fetched ONCE per run, not twice. The round trip is the smaller half — it is mutable config, so two reads can disagree and the two arms then decide about different repo states with neither able to notice. #858: `verdict_script` resolves from `$repo_root`, not `$CLAUDE_PROJECT_DIR`. And the finding that mattered more — `ETV_HOOK_FIRE_LIB` is `. `-SOURCED, so it is CODE running before stdin is read and before `decide` exists. A first draft exempted it as "telemetry, not a predicate"; cold review refuted that by execution: a decoy hook-fire-log.sh in an env-var-named tree printing an allow and exiting 0 GRANTS THE MERGE, bypassing every check. Classify a path by how it is CONSUMED, never by what it is called. This hook's copy is self-located; the other twelve are #891 (high/security), which records the reachable case — husky launches the prepush hooks by RELATIVE path, so the two roots diverge there. check-required-contexts.sh gains an array-type gate (a JSON object previously printed `nomatch`, a positive claim about server config from a body it cannot consume). Verification: 1377 passed / 2 skipped; 11 declared mutants, 11 detected, disjoint reddened sets; classifier executed across jq 1.8.2 and 1.6 with identical results; both env-var tests ship a negative control, because the passing outcome is also what an inert decoy produces. Four cold review rounds plus a bounded prose check. Every round found defects the previous round's fixes introduced — a type conflation that re-opened the auto-grant, a comment asserting the opposite of the line its own commit changed, and a corrected sentence whose identical twin survived in the same diff. Docs: new record `process.hook-resolves-inputs-from-repo-root`; both inline sites cite it rather than arguing it twice. docs/remote-state-inventory.md's row for the second read updated. Follow-ups filed: #891 (the other 12 hooks), #895 ("all N tests green" claims). fixes #858 fixes #859 Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be> |
||
|
|
761e575836 |
fix(787): derive the dropped-step guard's scope, and reconcile its snapshot against the server (#861)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 6s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m45s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m22s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m56s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m33s
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be> |
||
|
|
35affecd29 |
docs(755): ersatztv owns the fork code, media-management owns channel operations (#838)
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 8s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 20s
Build ErsatzTV Image / Build & test (.NET) (push) Failing after 1m41s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m7s
Build ErsatzTV Image / Build & push image (amd64) (push) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m5s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be> |
||
|
|
7d81f85b38 |
fix(778): make the by-name pin falsifiable — it was filtering on the property it asserted
Confirmation pass returned BLOCKED, and its lead finding is the one worth having.
The test forbidding the by-name lookup recorded URLs from INSIDE the
`endswith("/branch_protections")` branch, so the only URLs it could ever record were
ones that already satisfied the assertion. A by-name request was invisible to the very
test written to forbid it. Cold review proved it by reintroducing the lookup in the
hook: the suite stayed 33/33 green. That is the filter-on-the-asserted-property defect
this PR's sibling record exists to describe, committed inside the guard against it —
and the commit message had called the twin "pinned so it cannot come back".
The recorder now sees every branch-protection URL whatever its shape. Re-verified by
the same mutation: reintroducing a by-name call reddens exactly the two tests that
forbid it.
Also from that pass:
- an HTTP 404 on the LIST read reached the "the full rule list was read and none
matches" deny — a claim about a read that never happened. Gitea answers 404 for a
repo that is absent or invisible to the credential, so the classifier's own verdict
is now the sentinel `nomatch` and HTTP failures reach the ask;
- two comment blocks still described the deleted by-name endpoint as live, one of them
asserting the classifier "is never reached at all";
- the decision record still documented `branch_protections/{base}` and its 404
semantics as the mechanism, in the record this PR authored — now rewritten to the
list endpoint, with why reading the LIST is the load-bearing choice;
- seven assertions on a string the hook no longer emits, and three test
names/docstrings describing the removed 404 flow;
- an unused fixture helper, and 79 lines left over-indented by the removed nesting.
724 tests green.
refs #778
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
456042ee51 |
fix(778): stop claiming parity with Gitea's glob matcher — classify, or ask
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 27s
PR Gates / Docs update reminder (pull_request) Successful in 29s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 23s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
PR Gates / decisions lifecycle (pull_request) Successful in 28s
review-verdict/h10 Awaiting review verdict for 456042e
Review verdict / Set review-verdict status (pull_request_target) Successful in 15s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m56s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Canceled after 7m16s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Canceled after 0s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Canceled after 0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Canceled after 0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Canceled after 0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Canceled after 0s
Eighth cold review: no Blocker, no High. Three Medium, two Low, one Nit.
MEDIUM — the substantive one. The glob fallback asserted it matched rules "the same
way Gitea applies them", and it does not. Gitea compiles a rule name with gobwas/glob
and a `/` separator: its `*` does NOT cross a slash, `?`/`[…]`/`{a,b}` are wildcards,
and a plain name is folded case-insensitively. Mine used `.*` for `*` and escaped the
rest. The divergence has a false-OPEN direction — `release/*` does not govern
`release/26/hotfix` in Gitea, but `release/.*` matched it here, which would auto-grant
a scheduled merge on a base where the check is not required. That is #622's hole,
reached through the block written to close it, via exactly the failure this PR
records: a claim about an external system asserted rather than verified.
Reimplementing somebody else's glob dialect would be a second copy of a parser, which
this repo has already withdrawn a change for. So the classification is three-way and
each arm is safe WITHOUT knowing the dialect: an exact non-glob name folded
case-insensitively is decidable; a glob rule that could govern the base is
UNDECIDABLE and asks; and "could" is tested with a provable superset of any glob
dialect — literal prefix, `.*`, literal suffix — so if even that cannot match, no
dialect can. Over-matching would grant on unestablished protection; under-matching
would deny with a false cause. Asking is the only answer honest in both directions,
and it is rare: this repo's rule is the plain name `main`.
MEDIUM — a count that was wrong the moment it was written ("46 of the 69 rows are
N/A"; it is 44). It was added by the same commit that demoted two rows. That is the
FOURTH stale number in this change, in the deliverable whose own record argues against
hand-maintained counts. Removed rather than corrected, with the reason stated.
MEDIUM — `migration-smoke.sh` still said "Same shape" as `security-scan.sh`, whose
note had just been rewritten to the opposite conclusion, so the backreference had
silently inverted. It is the same pull-then-run over a mutable tag and deserves the
grade MORE, not less: `security-scan.sh` boots a throwaway container and authorizes
nothing, while this gates a production stack recreation. Regraded.
LOW/NIT: an `end <= start` guard that `str.index(…, start)` makes unreachable by
construction is replaced with the reachable failure it was describing; a docstring
still named a fixture from an earlier draft; a reflowed `#707.` was line-initial.
Two mutations were ineffective on the first attempt — one left the `decide ask`
continuation in place, the other had no test covering case-folding. Both redone; each
arm now reddens a named test.
Decisions-Edit: yes
refs #778
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
7db4101b24 |
fix(778): the escape emitted TWO backslashes, turning a false-open into a false DENY
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 13s
PR Gates / Docs update reminder (pull_request) Successful in 10s
PR Gates / decisions lifecycle (pull_request) Successful in 24s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 25s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 21s
review-verdict/h10 Awaiting review verdict for 7db4101
Review verdict / Set review-verdict status (pull_request_target) Successful in 28s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m56s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m37s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m14s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m8s
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 5s
Seventh cold review. One High, one Medium, three Low, seven Nit — all in the two newest commits, which is where every round of this PR has found its defects. HIGH, and it is my own fix from the previous commit. In jq source `"\\\\"` decodes to TWO backslashes, so escaping produced `\\.` — "a literal backslash, then any character" — instead of an escaped dot. Every rule name containing a metacharacter became UNMATCHABLE, and a rule named `a[b` crashed jq outright (swallowed by `|| true`). Verified: `release/26.*` no longer matched base `release/26.4`, so the fallback found nothing and hard-DENIED with the stated cause "has NO branch protection at all" — converting a false-open into a false deny, which the block's own comment calls the worse outcome. One character: `"\\" + .c`. Correct across 14 rule/base pairs. WHY MY TEST MISSED IT, which is the transferable part: it asserted only the NEGATIVE direction (`mai.` must not match `main`). A rule matched literally and a rule made unmatchable both fail to match the wrong base, so the assertion passed for the wrong reason. Only a rule that SHOULD match separates them, and there was no positive control. There is now — plus a char-class case — and both go red against the over-escaped version. That also needed a base containing a dot: a rule cannot carry a metacharacter and still match `main`, so the first attempt at the positive control was unsatisfiable by construction. MEDIUM — four live claims that the population "derives from the filesystem", left standing by the commit that replaced that mechanism: the guard's own docstring 45 lines above a comment shouting the opposite, the inventory heading 21 lines under "Every git-tracked file", the docs/README entry, and — worst — the record's `mechanics:` frontmatter, which is the copy the catalog and MemPalace mirror, so discovery would have returned the superseded lesson. All corrected. LOW/NIT: the URL-encoding test grepped the source for `@uri` (it now asserts the URL actually requested, and reddens when the encoding is removed); the hoist comment said "every path below" without noting the docs-only enumeration above it (bounded — that path is a passthrough to a human prompt, never a grant); a now-unreachable guard is annotated rather than left reading as live; `issue-qualification-audit.sh` was `N/A` while `select-queue.sh` was `UNSAFE-KNOWN` on the same argument, and `security-scan.sh` claimed "one step" for a pull-then-run over a mutable tag — both regraded; the `PINNED` definition now says what separates its second shape from an `N/A` "one step" row (the identifier's immutability, not the step count); the section parser raises a message naming both required headings instead of a bare ValueError; and the record's body is rewrapped. Decisions-Edit: yes refs #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e5996b7c1e |
docs(778): the population was wrong three times — record what finally fixed it
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 19s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 12s
review-verdict/h10 Awaiting review verdict for e5996b7
Review verdict / Set review-verdict status (pull_request_target) Successful in 16s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m47s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Canceled after 7m29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Canceled after 0s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Canceled after 0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Canceled after 0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Canceled after 0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Canceled after 0s
The record said "wrong twice" and drew the lesson "execute the traversal and eyeball it". The third instance (rglob picking up untracked `.husky/_` shims) shows that was still the wrong generalisation: every round had executed its traversal, and every round had an argument for why it was sufficient. What held was changing the SOURCE, not the walk — `git ls-files` instead of the filesystem. The index is authoritative, identical for CI and every checkout, and excludes untracked build output by construction. So the lesson is the one `testing.guard-derives-population-from-source` already states, one level up: ask what the authoritative list of these things IS, and if the answer is "whatever the walk finds", the guard is not finished however carefully the walk is written. Decisions-Edit: yes refs #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
25048d9ea4 |
fix(778): round 3 — the fence covers BASE aliasing only, and stop citing a moving count
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 25s
PR Gates / Docs update reminder (pull_request) Successful in 21s
review-verdict/h10 Awaiting review verdict for 25048d9
Review verdict / Set review-verdict status (pull_request_target) Successful in 19s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m1s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m43s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Canceled after 22s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Canceled after 0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Canceled after 0s
Third cold review: no Blockers, 1 High / 5 Medium / 1 Low / 1 Nit. All accepted. It independently re-derived the 59-file population and matched it against `find`, so the traversal that was wrong in rounds 1 and 2 is now verified rather than argued. HIGH — the ABA claim was too broad. `ci.verdict-write-retarget-fence` counts `change_target_branch` events, so it fences the BASE axis and nothing else. A force-push H1 -> H2 -> H1 during pagination leaves the final `.head.sha` comparison equal while the middle pages were enumerated against H2, and no counter moves. Two rows implied the fence covered that; both now state the head residual as real and unfenced, with what closing it would take. Also: the record still said the scheduled-merge residual was "closed one layer down" by the branch protection an admin may have removed — the circular sentence that was rewritten in the inventory last round and left standing in its twin. The hook header still called an immediate merge "sound". Both now describe the bounded window. `docs-reminder` was over-demoted by grouping it with `decisions-guard`: it cannot fail its job, so it authorizes nothing and is N/A, while `decisions-guard` reaches the combined status. Split, per this file's own rule that differing classifications get separate rows. Over-demotion is a defect too — it makes the column noise. The scope heading and the docs/README entry now name the `scripts/tests/` exclusion explicitly, so nobody adds a remote-reading test executable expecting a red guard that stays green, and a wrong limit cross-reference is corrected. The exclusion's justification was also factually false — it claimed the only network calls were to PATH stubs, but test_hook_fire_log.py starts a real http.server on 127.0.0.1 and drives it with real curl. The exclusion stands on confinement, not on absence, and now says so. COUNTS. "4 of 68 PINNED" was wrong (3), and rewriting it as "3 of 68" went stale in the same commit when splitting a row moved the denominator to 69 — three stale figures in three rounds, in the record warning against exactly this. The exact denominator is gone: three rows survive as PINNED out of roughly seventy, and the load-bearing claim is "almost nothing is pinned", not an integer. A hand-maintained count is a second copy of the table; guard-inventory.md gives its counts an equality check because they ARE the point, and a rationale record should not pretend to. refs #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
84b4c11188 |
fix(778): round 2 — recursive population, and grade down five more overclaims
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 22s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 14s
PR Gates / Docs update reminder (pull_request) Successful in 23s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / decisions lifecycle (pull_request) Successful in 24s
review-verdict/h10 Awaiting review verdict for 84b4c11
Review verdict / Set review-verdict status (pull_request_target) Successful in 22s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m10s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m22s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Canceled after 5m4s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Canceled after 0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Canceled after 0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Canceled after 0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Canceled after 0s
Second cold review: no Blockers, 4 High / 1 Medium / 2 Low. All accepted. POPULATION, WRONG A SECOND TIME. Round 1 removed a content filter that had omitted `git fetch`. Round 2 found the replacement traversal used non-recursive `Path.glob`, so four nested files were still outside it — including scripts/scripted-schedules/entrypoint.py, which calls get_context() against a live ErsatzTV server and then drives define_content/reset_playout/build_playout off the result. Now rglob, with scripts/tests/ as the single stated DIRECTORY-level exclusion (a scope choice, reviewable in one line; not a predicate over content). Population 55 -> 59, rows 63 -> 68. The generalisation is in the record, because the deliverable made the same mistake twice: the scope may be hand-written, but anything narrowing the POPULATION has to be executed and its output compared against the filesystem — the members it drops are invisible by construction. That is the #774 rule turned on the artifact meant to enforce it. FIVE MORE OVERCLAIMS GRADED DOWN. Both merge-consent head/base rows (the hook returns `allow` and a separate call merges, so the window is small, not absent — "no async window" was simply false); the release smoke pull and the ci-image verify (the concurrency group is PER-REF, so a branch build and a tag build of one commit can publish the same :<short-sha>); and the workflow base-fetch rows, which are not advisory — the merge hook reads the COMBINED status, so any red context blocks the auto-grant. Also fixed a stale cross-reference where the enumeration row still said it "inherits that row's pins" from a row graded down in the same commit. Four PINNED rows survive out of 68. That ratio is the honest finding. CIRCULAR JUSTIFICATION REMOVED. The scheduled-merge row said its residual was "closed one layer down" by the very branch protection an admin may have removed. It is not closed, it is BOUNDED by a trust assumption, and the row now says so. Low: jq's `//` fires on `false` as well as null, so `status_check_contexts: false` was defaulted to [] and produced a confident deny from a shape never understood — absent and null are now defaulted explicitly, everything else is "unknown". And the title sentence claimed "every executable in this repo" while the guard covers four directories; both it and the docs/README entry now say what is actually enforced. refs #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a0849eec58 |
fix(778): address the cold review — drop the population filter, grade down three overclaims
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 7s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 18s
PR Gates / decisions lifecycle (pull_request) Successful in 24s
PR Gates / Docs update reminder (pull_request) Successful in 9s
review-verdict/h10 Awaiting review verdict for a0849ee
Review verdict / Set review-verdict status (pull_request_target) Successful in 17s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 13s
PR Gates / Script tests (pytest) (pull_request) Successful in 3m58s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m43s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m30s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m29s
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
Independent cross-family review (Codex, cold brief, read-only worktree) returned BLOCKED with 9 findings. All 9 accepted; #5 partially, keeping one row PINNED with its justification stated. POPULATION (the finding that mattered most). The derivation filtered the scope by an outbound-network token list and argued that was a scope choice rather than a population filter. It omitted `git fetch` — this repo's most common remote read — so prepush-rebase-check.sh, which fetches origin/main and derives a PUSH DECISION, was structurally invisible to a guard claiming to cover "every executable that reads live remote state", along with three others. The defence offered was that over-inclusion is the safe direction; the filter also under-included. The content filter is gone: the population is now all 55 files in the scoped directories, and a file that reads no remote state carries an explicit N/A row. OVERCLAIMS, graded down. Three rows asserted more than the code does: - the scheduled-merge path was PINNED while the hook's own comment concedes the branch-protection read pins nothing → UNSAFE-KNOWN, "preflight, not a pin"; - pr-changed-files.sh was PINNED and claimed "any movement fails", but before-and-after equality is ABA-vulnerable (main → scratch → main) → UNSAFE-KNOWN, pointing at the caller-side event-count fence that does close it; - the CI toolchain image was PINNED on a mutable TAG, against this file's own definition naming a digest → UNSAFE-KNOWN. The release smoke pull stays PINNED: it pulls the tag the same concurrency-serialized job just pushed. The guard-inventory MUTATION regrade is reverted to BEHAVIOUR-ONLY (8/6/20). The review is right on species: the test feeds the real script an input the clause rejects, which this table explicitly defines as behaviour-only and has already regraded three rows for. A manually-executed disarm does not change what the test is. TWO REAL FAIL-OPENS FIXED: - jq `index()` on a STRING is substring search, so a status_check_contexts arriving as "prefix-review-verdict/h10-suffix" answered yes and would auto-grant. Membership is now exact equality over a value first proven to be an array of strings. - post-review-verdict.sh guarded both re-read comparisons with `[ -n "$x" ] &&`, so a 2xx body that merely omitted .head.sha or .base.ref made the check a no-op and the status was posted having confirmed nothing. That second fix carries a lesson worth the line: the obvious mutation (disarm the new `-z` arm) stays GREEN, because the unconditional `!=` also rejects empty — the two overlap, exactly the duplicate-guards-mask-each-other shape. The proof is taken against the REAL predecessor with the `-n` conjunct restored, which goes red showing returncode=0 and a status written. Also: 404 is now separated from 403/transport (an unprotected branch is the strongest form of the finding; `curl -sf` collapses both to an empty string), and the positive control asserts the decision is `allow` and that the endpoint was actually reached, rather than the absence of one phrase. refs #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fe578c5864 |
feat(778): name the check-and-use race, and verify the protection the merge gate rests on
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / Docs update reminder (pull_request) Successful in 24s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 26s
PR Gates / decisions lifecycle (pull_request) Successful in 31s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 10s
review-verdict/h10 Awaiting review verdict for fe578c5
Review verdict / Set review-verdict status (pull_request_target) Successful in 14s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m19s
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) Canceled after 4s
#773 §3 Family D is the one class its taxonomy had no bucket for: a check and the action it authorizes are separated in time over state that can change in between, with nothing pinning a version (#536, #622, #632, #706, #707). The repo had already solved this twice without noticing it was one problem — a compare-exchange claim in-process (ffmpeg.work-ahead-slot-atomic) and RFC 7232 If-Match across /api/v1 (concurrency.ifmatch-rfc7232) — and then solved it a third time from scratch for the tooling at #706/#707. Hence a class-level record rather than a fourth per-instance one. What the enumeration actually found, which none of the five records predicted: the merge-consent hook's scheduled-auto-merge path is safe only because `review-verdict/h10` is a REQUIRED status check on main. That is branch-protection CONFIG, it lives outside this repo, nothing compared the two, and the hook asserted it in a comment AND in the grant reason a human reads. Switch the context off and every word of that sentence is false while the hook keeps printing it and keeps auto-granting. The hook now reads the branch protection and treats it as three outcomes: present proceeds, unreadable asks, absent denies. Two defects were caught by the new checks themselves rather than by review: - the population test found .gitea/workflows/dependency-scan.yml absent from the first draft of the inventory (a sixth workflow the recon slice never listed); - self-review found the guard denying with a confident wrong reason when jq errors one level down on a malformed contexts member, so the word is now matched exhaustively rather than compared against "yes". Same swallow that survived the first fix in the #632 base-change guard. Detector D has no plausible linter, so the detector is detector A applied to an enumerated inventory: docs/remote-state-inventory.md classifies every in-scope executable, and scripts/tests/test_remote_state_inventory.py derives the population from the filesystem and asserts set equality both ways. Deferred with reasons stated in the inventory: select-queue.sh (advisory, authorizes no write), ci-detect-already-validated.sh (skip elides re-validation only, the image still builds), review-verdict.yml's status POST (Gitea offers no conditional write; already fenced by #706's retarget counter). Mutation proofs witnessed for both new guards, clause-level, not whole-file. fixes #778 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
84165ab755 |
fix(797): the BOM guard was fail-open wherever xxd is not installed (#798)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 27s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m38s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m25s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m27s
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be> |
||
|
|
b83e965994 |
docs: make subagent use explicit, and turn "claim an issue" into a check rather than a label
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 28s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 20s
PR Gates / Docs update reminder (pull_request) Successful in 24s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 29s
PR Gates / decisions lifecycle (pull_request) Failing after 24s
review-verdict/h10 Exempt: docs-only change (no code, no protected path)
Review verdict / Set review-verdict status (pull_request) Successful in 7s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 26s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 25s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
PR Gates / Script tests (pytest) (pull_request) Successful in 54s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
Two rules that were implied but not enforceable, both demonstrated by ersatztv#649 being implemented TWICE in parallel to completion on the same day. Subagents. The kickoff's HARD CONSTRAINTS already require parallel disjoint slices, mandatory independent review from a cold brief, and a named model/effort per dispatch — none of which is satisfiable without delegation. But nothing said so outright, and a generic client preamble of the form "do not use the Agent tool unless the user requested it" reads as a prohibition. Now stated plainly in both CLAUDE.md (always loaded) and the kickoff (pasted per session), with what to delegate and what to keep inline. Claiming. `in-progress` prevents duplicate PICKUP, not duplicate WORK — the record already said so, but step 3 told you to apply the label and nothing else. It now requires four checks first: an open PR whose body says `fixes #N`, a remote branch naming the number, a claiming comment predating the label (exactly what select-queue.sh's CLAIM? flag raises and deliberately leaves unresolved), and a fresh git fetch. Each fails differently; all four are cheap. Staleness. The second half of the #649 collision was reading origin/main once, at branch time, and not again across hours and four review rounds. A branch on a stale base computes its diff against that base, so `git diff origin/main` shows other sessions' merged work as DELETIONS and pushing it reverts them. Re-fetch before every push, rebase when it moved. process.parallel-session-claim carries the incident, including what worked: the merged implementation was better in one respect and the discarded branch's test coverage was salvageable, so diff the two before discarding yours. |
||
|
|
fba5233caf |
feat(610): split the decision corpus into one YAML-frontmatter file per record
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Failing after 23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m5s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
168 records -> docs/decisions/records/<area>/<topic>.md (163 active, 23 dirs) and docs/decisions/archive/<area>/<topic>.md (5 archived). The filename IS the key, so one-active-record-per-key becomes a filesystem property rather than a validator check, and supersession becomes a `git mv`. WHY: the monolith was a concurrency problem before an aesthetic one. A 3,900-line append target made parallel sessions collide -- PR #605 and PR #614 both hit append-vs-append conflicts during routine rebases, and hand-resolving those inside the corpus is exactly the operation the rationale-rewrite guard exists to police. HOW IT IS VERIFIED: a ~170-file diff cannot be meaningfully read, so correctness does not rest on reading it. The parser was taught BOTH formats first, so the body-diff guard parses the old form at the merge-base and the new form at head -- the migration validates itself, no bypass. The proof is a field-level equivalence harness: 168 records before and after, zero lost, zero gained, zero field mismatches, zero rationale bodies differing. Reviewers should scrutinise the harness; it is the actual evidence. What measuring caught that reading would not have: - ~500 lines sit OUTSIDE any record -- decisions.md's lifecycle schema and each topic file's preamble, mostly the only copy. Source files are kept and stripped, never deleted. They also cannot be filed per-area: topic files hold several areas and 4 of 23 areas span several files. - Archive discovery was a non-recursive glob; after the split it found ZERO archived records, surfacing as four bogus "supersedes points to unknown key" errors rather than an obvious failure. - ~32 live docs point into the corpus BY DATE, which the split dangles. Each stripped file now ends with a generated "Records formerly in this file" index, which also rescues the identical breadcrumbs in old issue comments. - decisions.md's "In this file:" list was 97 same-file anchor bullets that the split makes WRONG, not merely stale. Dropped; the generated index replaces them with links that resolve. The equivalence harness now runs against a checked-in FIXTURE, not the live corpus. The earlier version migrated the real tree, which made it a one-shot: the moment the migration landed there was nothing left to move and the tests failed for reasons unrelated to the code. A fixture keeps them testing the SCRIPT rather than the repo's current state. Keys preserved verbatim, warts included: `sched` (12) and `scheduling` (1) remain two directories for one concept. Renaming a key is not a move -- it changes identity, breaks the equivalence proof, and invalidates MemPalace's per-key drawers. Taxonomy normalisation is separate work. refs #610 |