A3 of milestone 387, completing phase A. A1 made the count true, A2
made it a durable state; this makes it something the operator can see
without going looking.
Turned out smaller than filed, because A2 revealed why the existing
`tier_limited` palette entry in FailingSourcesCard had never rendered:
the chip was being cleared by the same successful run that produced it.
The colour was already chosen.
Where it surfaces:
- SourceHealthDot gains a `no-access` grade. Deliberately its own grade
rather than folded into healthy (which hides it) or warning (which
sends the operator hunting for a break that isn't there). A source
with real failures still grades as failing whether or not it is also
gated.
- SourceRow gets an info-coloured lock chip in the status cell, which
was empty for these sources — they have zero failures. Placed ahead
of the backfill states: "we can't see this creator" is the more
useful thing to say than which walk phase it is in, and unlike those
it does not resolve on its own.
- A "No access" status filter, deliberately separate from "Has errors".
Without it a gated source is invisible in a long list, because it
correctly stays out of the failing rollup.
Left OUT of NeedsAttentionCard on purpose. That card's only affordance
is Retry, and you cannot retry your way into a subscription tier —
issue 1285 already gives the real escape hatch, since disabling a
source clears its state. Nothing structural needed changing: the card
is fed by consecutive_failures > 0, which a tier-limited source never
has.
The count lives on the download event, not the source, so `list()`
joins it in with one DISTINCT ON query — selecting the run_stats
sub-object rather than whole metadata blobs, which carry up to 500KB of
truncated stdout each. Scoped to tier-gated rows only, so a healthy
library issues no extra query at all. Absent stays None rather than 0,
and both UI surfaces phrase the state without a number when it is
missing instead of printing a fabricated zero.
Also covers A1's live gated count, which shipped untested, and extends
the mount helper with slot stubs: SourceHealthDot puts the dot in a
NAMED slot, and unresolved Vuetify components render default slots
only — so those assertions would have found an empty wrapper and
passed vacuously.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNXXULQDjVZmbuNa2G9mD9