fix(intents): an ADB-off entry never scans, and a truncated detail is never believed (t21)

The t18 review's three findings, closed with the reviewer's own repro.

F1 (HIGH — the harm was real shells, not a wrong label). `async_scan_intents`
decided between `adb_disabled` and `adb_unavailable` INSIDE `if self._poller is
None:`, so a manager that HELD a poller fell through to the shell loop: with
`adb_enabled: False` and a connected poller the review measured status `fresh`,
one candidate CACHED, and `pm dump com.x …` really sent. The option is a fact
about the ENTRY, so it is now checked once at the top and returns before
anything else — no cache read, no cache write, no shell. The no-poller branch
keeps the OTHER honest answer: with the option ON and nobody to ask, the status
is `adb_unavailable` ("wait or reconnect"), never `adb_disabled` ("go turn the
option on"), because the two have different fixes. Five tests pin it, including
a cache-leak case (a manager with a cached answer whose option is then switched
off answers `adb_disabled`, not the cache) and both statuses with a FORCED scan.

F2 (low, but it fabricated a value that looked real). A capture cut mid-detail
produced `Scheme: "ht` → scheme `"ht` and `Authority: "www.exam` → that
authority, and `_candidate_data` would then prefill the form with a URL no app
can match. Not an injection (everything stays quoted when used), which is
exactly why nothing caught it. `_unquote` now DROPS a value that opens a quote
and never closes it (a lone quote too), so the callers turn it into None: the
candidate survives with its intact action/component and simply carries no
scheme/authority/path — and therefore prefills NO data instead of a broken URL.
A complete quoted value is still read (the anti-overcorrection pin), and the
consequence is pinned at the consumption end too, through the real options-flow
helper.

F3 (low). The floor comment now answers the review in writing, where the next
reader looks: the t18 review's measurement (awaited_calls 6 against a floor of 5,
three other counters within 2) is named, and the verdict is recorded — those ARE
the policy margins (2 for owner-counters because an owner really can be
refactored away, 1 for awaited_calls because six sites cannot carry a percentage
band). The counters were genuinely updated, not dodged: raised to the real sweep
when the code grew (20→24, 70→75, 235→275, 55→63, 1400→1620) and re-derived
twice since (t20 1620→1615, t21 1615→1617 for this unit's blocks).

EVIDENCE THAT THE TESTS PIN THE FIX (not merely pass): the same files were run
against the PRE-fix source (git archive HEAD, a throwaway copy under /tmp) — 9 of
the 11 new tests FAIL there, including the reviewer's repro, which fails with
`assert 'fresh' == 'adb_disabled'`. The two that pass are the deliberate
anti-overcorrection pins: a COMPLETE quoted value is still parsed, and the
no-poller control keeps its own status.

NO REGRESSION: 900 passed / 5 skipped, 901 / 4 with androidtvremote2; the
injection proof, the fixture's candidates with dedup/flagging/order/bound
disclosures, both cache outcomes, the registration round trip through the real
FlowManager, button lifecycle and defaults, the YAML seed rule and §7.1
(supports_response optional, a real return_response call, a no-response call) all
still pass. The strings files are byte-identical (untouched here). Real-HA pattern
collected and RAN 3 files → 74 passed / 0 skipped. Drift exit 0 (1650 blocks).

NOT VERIFIED HERE: the device side remains a scripted double — no real Shield was
asked for its resolver table, so the shell COUNT and the status vocabulary are
what is proven, not real firmware behaviour. No browser either: the form prefill
is asserted on the values the helper returns.
This commit is contained in:
James Mackay committed 2026-10-12 13:14:34 +01:00
1 parent 6b866019ef
commit 8f064b3247
5 files changed
+264 -16

No files matched your search

+23 -7
View File
@@ -143,7 +143,7 @@ HA_CALL_OWNERS: dict[str, tuple[str, str]] = {
#: Non-vacuity floors — the ONE source of truth for the standalone entry point
#: and the pytest wrapper (t39 F1). They sit below the current walk (26 modules,
#: 77 imports, 282 relative imports, 65 checked arguments, 6 awaited HA calls,
#: 1648 statement blocks) so a broken or mis-targeted walk fails loudly instead of
#: 1650 statement blocks) so a broken or mis-targeted walk fails loudly instead of
#: printing zeros and exiting 0.
#:
#: THE MARGIN POLICY (t20/O3 — the t19 review asked for this in writing). A floor
@@ -166,11 +166,24 @@ HA_CALL_OWNERS: dict[str, tuple[str, str]] = {
#: t19 complaint: at 1300 vs 1322 a 23-statement refactor tripped it). A 2%
#: band leaves a normal refactor alone while a parse failure still shows up as
#: a large drop.
#: * awaited_calls: 1 (5 vs 6). Stated plainly: with six awaited HA calls a
#: percentage margin is meaningless, so this floor is the weakest one here. It
#: is still worth having — a walk that stopped visiting `await` sites at all
#: trips it — but a deliberate removal of an awaited call MUST lower this floor
#: in the same commit, and that is the reviewable act the gate exists for.
#: * awaited_calls: 1 (measured 6, floor 5 — DELIBERATE, see below). Stated
#: plainly: with six awaited HA calls a percentage margin is meaningless, so
#: this floor is the weakest one here. It is still worth having — a walk that
#: stopped visiting `await` sites at all trips it — but a deliberate removal of
#: an awaited call MUST lower this floor in the same commit, and that is the
#: reviewable act the gate exists for.
#:
#: t18/F3 asked whether these margins are deliberate and whether the counters were
#: genuinely updated rather than dodged. Both answers are yes, and they are recorded
#: HERE rather than in a task log: the t18 review measured awaited_calls at 6
#: against this floor of 5 and three other counters within 2 (modules 26/24,
#: imports 77/75, call_args 65/63 — each named in its own comment below), and the
#: verdict is that those ARE the policy margins above: 2 for owner-counters because
#: an owner really can be refactored away, 1 for awaited_calls because the counter
#: is too small to do better. The counters themselves were RAISED to the real sweep
#: when the code grew (20→24, 70→75, 235→275, 55→63, 1400→1620) and re-derived
#: twice since (t20: 1620→1615; t21: 1615→1617 after this unit's blocks), never
#: lowered to dodge a failure and never left above the walk.
#:
#: The rule for changing any of them: lower a floor in the SAME commit that removes
#: the code it counts, and never raise one without re-running the sweep first (a
@@ -231,7 +244,10 @@ FLOORS: dict[str, int] = {
# t20: the walk is 1648 after the protocol lifecycle fixes (the tracked
# attempt, the cancel-and-await stop and the transition banner are new blocks),
# so the floor is re-derived from the CURRENT walk at the 2% band above.
"statement_blocks": 1615,
# t21: the walk is 1650 (the ADB-off guard returns before the scan, and the
# unterminated-value check is a few blocks more), so the 2% band is re-derived
# again: 1617.
"statement_blocks": 1617,
}
#: The interpreter floor Home Assistant itself declares: PyPI metadata for