Commit Graph
12 Commits
Author SHA1 Message Date
timothyandClaude Fable 5.1 fcdc381246 docs(568): the rule the branch establishes gets a record, and every row the fix moved is re-measured
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 7s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 13s
PR Gates / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / decisions lifecycle (pull_request) Successful in 18s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 12s
Review verdict / Set review-verdict status (pull_request_target) Successful in 26s
review-verdict/h10 Review-verdict: MERGEABLE @ fcdc381 (base: main)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m23s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m23s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 17m33s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m7s
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
api-conventions.md now says which half of an id-list validator a sibling field may
gate (existence, never the raw-count cap) and that a lost-race recovery re-asks the
whole validator set rather than the fields whoever wrote the catch remembered.
Those, with the bound and the field-named 422, are one convention with residuals, so
they get a record -- api.top-level-id-list-validation -- and a task-signal row.
The record states what #568 does NOT settle: three validators on two DTOs is a
per-field constant, not the repo-wide rule #917 owns, and it says to expect #917 to
replace the mechanism.

graphics-elements.md: rows 35-44 re-measured against the whole ErsatzTV.Tests
project on this tree, because the fix moved five of their red sets -- Validate is
now also what the recovery path re-runs, so removing a validator from it reddens
that handler's race test too. Rows 45-47 are new and measured the same way. The
"redden more than one test" figure is recounted from the table (21 -> 24); the
cross-fixture set is unchanged at five.

The negative discriminator rows now carry a stated seeding rule: vary one half of
the identity and hold the other at the seeded value. Varying both leaves the row
rejected by the pre-#568 predicate as well, so a composite revert to it would pass
every test at that site.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 3541683c99 docs(568): re-measure every mutation row the merged identity and the shared validator moved
Nine rows of the mutation table in docs/graphics-elements.md name a clause that this
branch's last commit moved, merged or gave new callers, and a red set is a measurement of
the tree it ships in. All were re-taken against the whole ErsatzTV.Tests project
(2139 tests, 6 skipped) on the code as it now stands, and four new rows added for the
clauses the fix introduced.

What moved and why the numbers changed:

- Row 10 was the seeded-path filter alone. `Kind` now lives inside `IsOnNowNext`, so
  dropping the lookup's `Where` drops both halves at once and reddens four tests, not
  three.
- Row 18 was the seeder's SQL `Kind == Text` filter and is now the `kind` conjunct of the
  shared predicate, so it reddens the API site too — a second cross-fixture row.
- Rows 35-37 pick up the count-cap tests, since the cap rides in the validator they
  disarm. Rows 33, 34, 38, 39 re-measured unchanged.
- Row 40's mutation text follows the API call's new two-argument shape; it reddens the new
  wrong-kind test as well.
- Rows 41-44 are the new clauses: the raw-count cap (one clause, three call sites, which
  is what its red set shows), the diagnostic-id truncation, and the two lost-race catches.

The two self-counted figures above the table were recounted from the table itself rather
than adjusted: twenty-one multi-test rows and five cross-fixture ones (13, 18, 22, 33, 41).

The deco lost-race test is renamed so no two rows cite the same test name.

api-conventions.md gains the three rules the fix establishes for any write path with a
top-level FK id list — bound the raw list, name the field, translate a lost check-then-write
race — in the handler-hardening checklist where they belong rather than as a #568 anecdote.

Refs #568

Decisions-Edit: yes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 722f0057e8 docs(568): bound the "cannot fail" claim to the two rows that measure it
The sentence explaining why the wrong-kind-and-wrong-folder case is not shipped said
"no single-clause mutation can let it through" — an unbounded quantifier over a
population nothing here measures. What is actually established is narrower and is
established: rows 10 and 18 are the two clauses of `GetBuiltInElementId`, each measured,
and dropping either leaves the other rejecting such a row. The claim now says that, and
names those rows as its evidence.

Refs #568

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 7fa223a567 test(568): drop the different-kind case no single-clause mutation can redden, and measure the API-site clause
`Ignores_A_Same_Named_Element_Of_A_Different_Kind` seeded an Image row at
`/templates/image/on-now-next.yml` and asserted the backfill ignores it. Under the
bare-filename lookup that row was rejected by the `Kind == Text` filter alone, which is
what its comment described. Under the full-path predicate the path rejects it first, so
neither clause is load-bearing for it: dropping the `Kind` filter reddens only
`A_Row_Of_Another_Kind_At_The_Seeded_Path_Does_Not_Suppress_The_Built_In_Row` (row 18) and
dropping `IsOnNowNext` reddens only the three tests of row 10. The test survived both and
its comment claimed a mechanism it no longer exercised. Its scenario is the conjunction of
two already-pinned negatives and is strictly weaker than
`Ignores_A_Same_Named_Same_Kind_Element_Outside_The_Seeded_Folder`, so it is retired rather
than reshaped, and graphics-elements.md now says why the combination is deliberately not
shipped — otherwise the next reader re-adds it.

Row 40 records the API-side half of the discriminator, which had a measured red and no row.
Measured whole-project on this tree, `dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj`:
baseline `Failed: 0, Passed: 2123, Skipped: 6, Total: 2129`; with
`BuiltIn = GraphicsElementDefaults.IsOnNowNext(e.Path)` reverted to
`Path.GetFileName(e.Path) == GraphicsElementDefaults.OnNowNextFileName`,
`Failed: 1, Passed: 2122`, the sole red being
`GetAllGraphicsElementsForApi_Should_Not_Mark_Same_Filename_Outside_Seeded_Folder_As_BuiltIn`.
The table's two self-counts were recounted from the table after adding the row and both
still hold: sixteen rows redden more than one test, three of those span two fixture classes
(13, 22, 33).

Refs #568

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 9f0f21ec52 docs(568): measure the five validator mutation claims the tests assert in prose
Five test comments asserted "reddens if <validator> alone is removed" with
nothing binding the sentence to a measurement -- the shape
testing.mutation-claims-are-executed refuses, and the shape whose CLAIMS half of
the manifest cannot reach a .NET proof. The repo's record for those is the
mutation table, so each claim got a row: all five mutated in turn against this
tree with the whole ErsatzTV.Tests project re-run (the tuple-arity fix included,
since a mutation that does not compile is not a result).

35 GraphicsElementIdsMustExist out of UpdateChannelHandler.Validate -> 2 red;
36/37 the deco graphics/watermark validators out of UpdateDecoHandler.Validate
-> 1 red each; 38/39 the two Consumes* mode gates -> 1 red each. Sixteen rows now
redden more than one test; three still span two fixture classes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 41fd1f64de docs(568): re-measure the rows the seeder's lookup call moved, and recount the table's own claims
The seeder now resolves the built-in row through GetBuiltInElementId, which puts
that lookup on a second call path, so every row whose clause the new call can
reach was re-run against this tree: 10 and 33 unchanged, 18 reinstated (the
Kind == Text filter has a red now that a wrong-kind row at the seeded path can
suppress the row the lookup needs), 21 unchanged, 22 gains a third red, and 34
is new (the existence check re-derived as SQL instead of asking the lookup).

Two stale measurements went with it. The "known clauses with no red" bullet for
the Kind filter quoted 2121 passed against a tree that produces 2123, having been
taken before the branch's last two tests existed -- the whole bullet is gone now
that the clause has a red. And the per-fixture-filter trap counted thirteen
multi-test rows with two spanning two fixture classes, true on origin/main and
false here since the branch added rows: fifteen and three, both recounted from
the table, with a note that they are.

Decisions-Edit: yes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 cb11116d4f docs(568): stable row ids in the mutation table, and the case-sensitivity sign-off the issue's wording invites
Two review findings, both about docs the branch already rewrote.

The mutation-coverage table in docs/graphics-elements.md ended up with two rows
numbered 24 -- the new IsOnNowNext row was inserted after 23 without checking
what followed -- while 18 was vacated when the Kind-filter row moved to the
"no red" list. The section's own prose cites rows by number ("the IsOnNowNext
clause (row 10)"), so a duplicate id makes a citation ambiguous. The new row
becomes 33, the next unused number, and the rule that made it 24 in the first
place is now written down: a row number is an identity, not a position, so a new
row takes the next unused number, nothing is renumbered, and a retired clause
leaves its number vacant rather than having it reused under a new meaning. Both
row claims were re-measured and are unchanged; only the id moves.

#568's second half is titled "builtIn discriminator is filename-only,
case-sensitive, folder-agnostic", and the branch removes the first and third
while deliberately keeping case sensitivity -- which reads like two thirds of a
done-when box. It is not: the remedy the same box prescribes, "full seeded
relative path", is exactly as case-sensitive as the filename match it replaces,
so the three adjectives describe one predicate rather than name three separable
demands. Read the other way the box would be unsatisfiable by its own remedy.
The reason case sensitivity is kept -- a case-INsensitive test hands the built-in
identity to a user element differing from the seeded path only in case -- lived
only in GraphicsElementDefaults.cs, where a reader arriving from the issue title
would not find it. It is now in the active record that owns the discriminator.

Refs #568

Decisions-Edit: yes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:15 +02:00
timothyandClaude Fable 5.1 ae314aa5ea docs(568): re-measure the built-in lookup's mutation rows against the code as it now stands
The mutation-coverage table's claims were measured against an earlier shape of
GetBuiltInElementId and are re-taken here, because the lookup changed twice on
this branch (filename -> seeded path, then SQL -> in-memory IsOnNowNext) and a
claim about which tests a mutation reddens does not survive either move on its
own.

Measured 2026-09-05, each mutation applied alone to the committed tree:

- Row 10, the seeded-path filter removed: 3 red, not the 2 the row listed.
  Ignores_A_Case_Variant_Of_The_Seeded_Path joins the two already named,
  because without the filter every Text row resolves as the built-in one.
- Row 24 is new: IsOnNowNext loosened from Ordinal to OrdinalIgnoreCase reddens
  exactly the two case-variant tests, 2 failed / 77 passed. One row covers both
  discriminator sites because they now share the predicate.
- The Kind==Text filter's "no red" bullet is re-measured across the WHOLE
  ErsatzTV.Tests project -- 2121 passed, 6 skipped, 0 failed -- rather than the
  11 tests of the one file that names GetBuiltInElementId. ChannelGraphicsDefaults
  reaches the lookup from the channel-create handlers as well, so the narrower
  population could not have seen a red there. The conclusion is unchanged; what
  changes is that it is now measured over the population that could falsify it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:14 +02:00
timothyandClaude Fable 5.1 131c63f7f4 fix(568): one ordinal identity predicate for the built-in element, applied in memory at both sites
The branch moved the `builtIn` discriminator from a bare filename to the full
seeded path, but split how the two sites evaluate it: the API handler compares
in memory (ordinal) while GetBuiltInElementId's new `.Where(e => e.Path ==
OnNowNextSeededPath)` compares in SQL. GraphicsElement.Path takes no explicit
collation -- TvContext.OnModelCreating pins one only on the listed name/title
columns -- so SQLite answers that case-sensitively and MySQL uses the server
default, which is normally case-INsensitive. On MySQL the two discriminators
could therefore disagree about the same row: AttachOnNowNextByDefault would
resolve a case-variant user element as the built-in one while the API reported
builtIn:false for it.

Collapse both onto GraphicsElementDefaults.IsOnNowNext, ordinal, applied in
memory. GetBuiltInElementId goes back to loading the Text candidates and
filtering in memory (the shape it had before this branch), keeping only the
`Kind` enum filter in SQL.

The prose claimed more than the code did. "A filename-only comparison is
case-sensitive-by-accident" appeared in four places as a defect the full-path
fix removed; a full-path comparison is exactly as case-sensitive, so the clause
said nothing and implied a fix that had not happened. Case sensitivity is now
deliberate and stated as such -- the built-in element is the exact file the
seeder wrote, at the exact path it wrote it to -- and the reason the comparison
is kept out of SQL is recorded where the predicate lives.

docs/decisions/records/graphics/channel-level-attachment.md said BuiltIn was
"computed by comparing the row's `Path` to GraphicsElementDefaults.
OnNowNextFileName", which was true of neither the pre-#568 rule (filename to
filename) nor the current one; an active record resolved by key now states the
current predicate in its own sentence rather than in a parenthetical.

Two tests pin the ordinal rule against a loosening to OrdinalIgnoreCase, one
per site. Measured: OrdinalIgnoreCase reddens exactly
GetAllGraphicsElementsForApi_Should_Not_Mark_A_Case_Variant_Of_The_Seeded_Path_As_BuiltIn
and Ignores_A_Case_Variant_Of_The_Seeded_Path, 2 failed / 77 passed of the 79
graphics tests. They do NOT pin provider independence -- under SQLite's BINARY
collation an equivalent SQL comparison answers identically, so no test in this
suite can distinguish the two. That is stated at each site rather than left for
a reader to assume the tests cover it.

Decisions-Edit: yes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:14 +02:00
timothyandClaude Fable 5.1 352305ade8 fix(568): sync docs/comments to the seeded-path builtIn discriminator, harden UpdateDecoHandler's twin FK ids
Review round on #568 found the branch changed builtIn identity from a bare
filename comparison to the full seeded path (GraphicsElementDefaults.
OnNowNextSeededPath) but left several places still asserting the old rule:

- docs/decisions/records/graphics/channel-level-attachment.md and
  on-now-next-on-by-default.md (both status: active) still described a
  filename-only match; corrected in place and cross-referenced.
- docs/api-conventions.md §8 quoted the retired
  `Path.GetFileName(element.Path) == OnNowNextFileName` expression verbatim;
  replaced with the current OnNowNextSeededPath comparison and a note on the
  UpdateChannelHandler 422 hardening.
- Three in-code comments (GraphicsElementDefaults.cs, GraphicsElementSeeder.cs,
  ChannelGraphicsDefaults.cs) still said "identity is the filename".
- docs/graphics-elements.md's mutation-coverage table (row 10, row 18) named
  clauses that no longer exist or no longer redden any test post-#568;
  re-measured directly (removing the seeded-path check reddens
  Ignores_A_Non_Built_In_Element_With_A_Different_Filename and
  Ignores_A_Same_Named_Same_Kind_Element_Outside_The_Seeded_Folder; removing
  the Kind==Text filter alone reddens nothing, so it moves to the "known
  clauses with no red" list with that measurement dated).

Also closed the should-fix twin: UpdateDecoHandler's graphicsElementIds and
watermarkIds are top-level ReplaceDecoRequest fields in the same position as
UpdateChannelRequest.graphicsElementIds (not the deep-FK-in-a-nested-list
carve-out), and the reconcile in ApplyUpdateRequest blindly Added a join row
for any incoming id -- the identical FK-constraint-to-500 defect #568 fixed
on the channel path. Added GraphicsElementIdsMustExist/WatermarkIdsMustExist
validators mirroring UpdateChannelHandler's, pinned by
UpdateDecoGraphicsElementsTests (reddens when either validator alone is
removed -- verified).

Decisions-Edit: yes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:14 +02:00
timothyandClaude Fable 5.1 c84bdd2427 docs(568): record the full-seeded-path builtIn discriminator in graphics-elements.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
2026-09-05 21:35:14 +02:00
timothyandtimothy ba6a4b08aa feat(732): On Now / Next gets a background box, and is on by default (#843)
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 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 25s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m40s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m18s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m12s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
probe742/combined-newest SECOND
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-26 19:28:25 +00:00