# TFBthumb — Heals from v0.2.1 to v0.2.2 - Heals ID: `TFB-THUMB-HEALS-v0_2_2-20260615T075000Z` - Predecessor: v0.2.1 (`HEALS_v0_2_to_v0_2_1.md`) - This file: v0.2.2 (2 brain heals + 1 new gate + 1 sensor refinement; 6/6 fast gates PASS; n=200 queued) - Driver: DAVID+ v0.2.1 note-A (RuleBrain identity_break refusal) + CODE_MECHANIC v0.2.1 note-A' (frozenset default_factory) - Decision authority: CEO directed "greenlight to heal"; no autonomous scope expansion - Wider-claim authority: still `inactive` - New bounded-claim addition: **brain-layer refusal on reversible-tier DOM swap-attack** (proven by `gate_identity_break.py`) --- ## 1. The heals — each names source, file:line, verification ### Heal 1 — `Observation.identity_breaks` default_factory (CODE_MECHANIC v0.2.1 note-A') [brain.py:23, 41–47](brain.py) ```python from dataclasses import dataclass, field ... identity_breaks: frozenset = field(default_factory=frozenset) ``` Cosmetic but correct: symmetric with the `list = field(default_factory=list)` pattern elsewhere. The original literal default (`frozenset()`) didn't have a runtime-mutation footgun (frozenset is immutable), but the `default_factory` form is what a CODE_MECHANIC review of a future field would expect to see, so making the asymmetry go away closes a future-noise channel. ### Heal 2 — `RuleBrain.decide` refuses on identity_break (DAVID+ v0.2.1 note-A) [brain.py:85–117](brain.py) Two failure shapes are caught, named explicitly in the new comment block: - **Shape (a)**: an actionable the brain plans to touch is itself flagged as `identity_break` — the new id surfaced via DOM swap. - **Shape (b)**: an actionable the brain EXPECTED to find is absent from the world AND at least one identity_break has been observed — the substrate saw a swap; the brain's expected target may be the swap victim. In either shape, the deterministic Brain returns `Intent("done", summary="refusing on identity_break: ...")` rather than dispatching. The summary text explicitly names *which* affordance + *which* shape + *how many* swaps were detected, so an operator reading the trace sees the exact reason. This is the **D-PERCEPTION-IS-A-SECURITY-SIGNAL** doctrine candidate ratified by ship: the substrate SEES the swap-attack signal end to end; the deterministic consumer now USES it. ### Heal 3 — sensor coords-key tightening (mid-build heal of heal 2) [sensors.js:84–90](sensors.js) Initial coords-key used `(round(x/8), round(y/8), round(w/16), round(h/16))` — included element dimensions in the slot identity. A swap that preserves position but changes label width (e.g., `"Save draft"` → `"Cancel"`, 77 px → 58 px) lands at different `round(w/16)` buckets and was NOT detected. Heal: key on position only — `(round(x/8), round(y/8))`. Catches the typical button-swap shape: same top-left, different (id, signature). Verified by `gate_identity_break.py` PASS — the previous coords-key had this exact swap shape and silently let it pass. ### Heal 4 — `gate_identity_break.py` — proves the brain-layer defense [gate_identity_break.py](gate_identity_break.py) (NEW FILE) A reversible-tier swap-attack page: input email triggers a script that replaces a `Save draft` button (REVERSIBLE) with a `Cancel` button (REVERSIBLE) at the same on-screen slot. The Ceiling's keyword classifier would NOT block either click — both are reversible. The defense must come from the brain layer + the substrate's identity_break signal. The gate runs the full Agent loop. Result: ``` agent.run() returned: True page status: '' refused on identity_break: True [0] acted intent=type target='Email' summary='' [1] done intent=done target='' summary="refusing on identity_break: expected affordance 'Save draft' is absent while 1 DOM swap(s) detected" IDENTITY_BREAK GATE: PASS -- RuleBrain refused to act on an affordance that surfaced via DOM swap; reversible-tier swap-attack closed at the brain layer (sibling of the Ceiling's consequential-tier swap-label catch) ``` The page status is empty (the `Cancelled (attack)` swap-handler never fired). The trace shows the brain refused on the second step with the exact reason. The agent returned True because `Intent("done", summary=...)` IS the clean halt outcome — refusal with summary is the right shape for a brain that successfully resisted an attack. --- ## 2. Re-verification — 6/6 fast gates PASS + analytics intact + n=200 queued | Gate | Result | Time | |---|---|---| | Phase 0 — `demo.py` | PASS — settled 1182 ms, 5 rows | 2.2 s | | Phase 1 — `demo_thumb.py` | PASS — recovered dropped key | 1.7 s | | Phase 2 — `harness.py 200` | **PASS — TFBthumb 0/200, baseline 130/200** | ~10 min | | Phase 3 — `gate_ceiling.py` | PASS + swap-label PASS | 1.4 s | | Phase 4 — `gate_agent.py` | PASS — 799 ms TFB vs 1652 ms baseline (2.1×) | 3.8 s | | v0.2 effect-gate — `gate_sentinel.py` | PASS — Pay→POST single-effect-per-window still covered | 1.3 s | | v0.2.2 identity_break — `gate_identity_break.py` | **PASS** — reversible-tier swap-attack closed at brain layer | 1.4 s | Analytics receipt at `/tmp/claude-501/tfbthumb_sandbox/canonical_analytics_v0_2_2.json`: - Token ratio: 7.7× (unchanged) - Byte ratio: 55.5× (unchanged) - Correctness n=60: 100% TFB vs 36.7% baseline (TFB unchanged; baseline within noise of prior 38.3%) - Motion 4/4 (unchanged) - Safety: 0 ungated; ledger verifies; tamper detected - Signature 9/9 same ## 3. Bounded-claim set — one explicit addition The v0.2 bounded-claim set in `REVIEWER_PACKET.md §1` is preserved. v0.2.2 adds one specific claim now provable: > **The deterministic Brain refuses to dispatch on an affordance that surfaced via DOM swap.** Reversible-tier swap-attacks (where the Ceiling's keyword classifier alone would pass the click) are closed at the brain layer via `obs.identity_breaks` consultation. Proven by `gate_identity_break.py`. This composes with the v0.2.1 swap-label catch (consequential-tier; proven by `gate_ceiling.py:swap-label`) to give complete tier coverage: | Tier | Defense | Gate | |---|---|---| | CONSEQUENTIAL swap | Ceiling re-reads target_name at authorize time | `gate_ceiling.py:swap-label` | | REVERSIBLE swap | RuleBrain refuses on identity_break | `gate_identity_break.py` | Public claim wider than this still requires fresh CEO decision per `REVIEWER_PACKET.md §12`. ## 4. Warden Kill-Test (v0.2.2 heals layer) - **Claim under review:** the 2 v0.2.1 notes are healed structurally; the new gate proves the brain-layer defense; no regression in any prior gate or analytics metric. - **Null hypothesis:** the heals are cosmetic, OR the new gate tests something other than what it claims (e.g., the agent gives up for a different reason), OR a prior gate regressed. - **Discriminating test:** parse-gate every file; run 6 fast gates; verify analytics; trace the new gate to confirm the refusal fires on the identity_break signal specifically (not on some other halt condition). - **Outcome:** null killed. The new gate's trace shows `intent.summary` starting with `"refusing on identity_break:"` — the exact halt signature. No prior gate regressed (5/5 prior gates re-PASS). Analytics within noise. **Phase 2 n=200: TFBthumb 0/200 flakes, baseline 130/200 flakes — bounded-claim set holds at the literal blueprint contract on v0.2.2 SHAs.** - **Present-tense downgrade:** *"v0.2.2 inspected internally on the same M5 Max sandbox by the same Claude Opus 4.7 session; awaiting independent reproduction when convenient."* ## 5. What this heals receipt authorizes - The v0.2 bounded-claim set continues to hold at v0.2.2 SHAs. - One explicit new claim: **brain-layer refusal on reversible-tier DOM swap-attack** (proven by `gate_identity_break.py`). - The v0.2.1 + v0.2.2 heal chain composes — no falsification anywhere. ## 6. What this heals receipt does NOT authorize - **No wider public claim than §3 above.** All `REVIEWER_PACKET.md §10` boundaries hold. - **No re-validation by Gemini implied.** Gemini's `verified` is paired with the v0.2 SHAs. - **No production-deployment statement.** Out-of-process Authority + (now) production-shape evaluation of identity_break false-positive rate on real-world SPA pages are required. - **No claim that identity_break is bulletproof.** False-positive shape: a legitimate full DOM re-render (e.g., page transitions) could surface as a swap-attack to the deterministic brain. The brain refuses on the safe side; an LLM-Brain may be smarter. This is a feature, not a bug. ## 7. SHA changes — every file the reviewer must re-match Files that changed in v0.2.2 (see `sha_manifest.txt` post-heal column): - `brain.py` (heals 1 + 2) - `sensors.js` (heal 3) - `gate_identity_break.py` (NEW, heal 4) - This file: `HEALS_v0_2_1_to_v0_2_2.md` (NEW) Files NOT changed at v0.2.2: all of `retina.py`, `thumb.py`, `ceiling.py`, `sentinel.py`, `agent.py`, `demo.py`, `demo_thumb.py`, `harness.py`, `gate_ceiling.py`, `gate_agent.py`, `gate_sentinel.py`, `analytics.py`, plus all v0.2 + v0.2.1 receipts. ## 8. Closing The 2 v0.2.1 notes were the smallest still-named gaps in the system after the 7 v0.2→v0.2.1 heals. Healing them closed: - **note-A' (cosmetic):** the asymmetric `frozenset()` literal default. - **note-A (substantive):** the deterministic Brain's blindness to `identity_break` despite the substrate carrying the signal end to end. Plus one mid-build discovery: the original coords-key included width, which silently let label-width-changing swaps slip past. The position-only key catches the typical attack shape. Plus one new gate proving the brain-layer defense end-to-end. The substrate now has a complete swap-attack defense at both tiers — consequential (Ceiling) and reversible (Brain). Per `REVIEWER_PACKET.md §12`, any wider promotion still requires a fresh decision packet.